From c34e32f60eee2c2d359113a8e2041c539563bdf4 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 21 Jan 2022 14:44:42 +0100 Subject: [PATCH 1/7] !!! Deprecate \Psalm\Plugin\RegistrationInterface methods Following \Psalm\Plugin\RegistrationInterface methods are deprecated + addFileTypeScanner + addFileTypeAnalyzer Following \Psalm\PluginRegistrationSocket methods are deprecated + addFileTypeScanner + getAdditionalFileTypeScanners + addFileTypeAnalyzer + getAdditionalFileTypeAnalyzers + getAdditionalFileExtensions + addFileExtension Mentioned methods will be removed in Psalm v5.0, corresponding functionality will be provided by following new interfaces and classes + \Psalm\Plugin\FileExtensionsInterface + \Psalm\PluginFileExtensionsSocket Related: #6788 --- src/Psalm/Plugin/RegistrationInterface.php | 2 ++ src/Psalm/PluginRegistrationSocket.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/Psalm/Plugin/RegistrationInterface.php b/src/Psalm/Plugin/RegistrationInterface.php index 475f63c77a0..a316ce088a5 100644 --- a/src/Psalm/Plugin/RegistrationInterface.php +++ b/src/Psalm/Plugin/RegistrationInterface.php @@ -17,12 +17,14 @@ public function registerHooksFromClass(string $handler): void; /** * @param string $fileExtension e.g. `'html'` * @param class-string $className + * @deprecated will be removed in v5.0, use \Psalm\Plugin\FileExtensionsInterface instead (#6788) */ public function addFileTypeScanner(string $fileExtension, string $className): void; /** * @param string $fileExtension e.g. `'html'` * @param class-string $className + * @deprecated will be removed in v5.0, use \Psalm\Plugin\FileExtensionsInterface instead (#6788) */ public function addFileTypeAnalyzer(string $fileExtension, string $className): void; } diff --git a/src/Psalm/PluginRegistrationSocket.php b/src/Psalm/PluginRegistrationSocket.php index 70d259d2e26..8d5340c44b1 100644 --- a/src/Psalm/PluginRegistrationSocket.php +++ b/src/Psalm/PluginRegistrationSocket.php @@ -143,6 +143,7 @@ public function registerHooksFromClass(string $handler): void /** * @param string $fileExtension e.g. `'html'` * @param class-string $className + * @deprecated will be removed in v5.0, use \Psalm\Plugin\FileExtensionsInterface instead (#6788) */ public function addFileTypeScanner(string $fileExtension, string $className): void { @@ -170,6 +171,7 @@ public function addFileTypeScanner(string $fileExtension, string $className): vo /** * @return array> + * @deprecated will be removed in v5.0, use \Psalm\PluginFileExtensionsSocket instead (#6788) */ public function getAdditionalFileTypeScanners(): array { @@ -179,6 +181,7 @@ public function getAdditionalFileTypeScanners(): array /** * @param string $fileExtension e.g. `'html'` * @param class-string $className + * @deprecated will be removed in v5.0, use \Psalm\PluginFileExtensionsSocket instead (#6788) */ public function addFileTypeAnalyzer(string $fileExtension, string $className): void { @@ -206,6 +209,7 @@ public function addFileTypeAnalyzer(string $fileExtension, string $className): v /** * @return array> + * @deprecated will be removed in v5.0, use \Psalm\PluginFileExtensionsSocket instead (#6788) */ public function getAdditionalFileTypeAnalyzers(): array { @@ -214,6 +218,7 @@ public function getAdditionalFileTypeAnalyzers(): array /** * @return list e.g. `['html', 'perl']` + * @deprecated will be removed in v5.0, use \Psalm\PluginFileExtensionsSocket instead (#6788) */ public function getAdditionalFileExtensions(): array { @@ -222,6 +227,7 @@ public function getAdditionalFileExtensions(): array /** * @param string $fileExtension e.g. `'html'` + * @deprecated will be removed in v5.0, use \Psalm\PluginFileExtensionsSocket instead (#6788) */ private function addFileExtension(string $fileExtension): void { From cad72004ab1a7b5339e4cba2239befa332341764 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 21 Jan 2022 21:52:22 +0100 Subject: [PATCH 2/7] Add deprecated method invocations to psalm-baseline.xml --- psalm-baseline.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index a3054781035..1a2dc6c1b8e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -34,6 +34,11 @@ $codebase->php_minor_version $codebase->php_minor_version + + getAdditionalFileExtensions + getAdditionalFileTypeScanners + getAdditionalFileTypeAnalyzers + @@ -611,6 +616,12 @@ array_keys($template_type_map[$template_param_name])[0] + + + addFileExtension + addFileExtension + + VirtualClass From 997d5d5f5f50898a7eda29011bd160f8fd2a0c93 Mon Sep 17 00:00:00 2001 From: orklah Date: Sat, 22 Jan 2022 17:37:35 +0100 Subject: [PATCH 3/7] handle two more cases of firstClassCallable --- .../Call/Method/AtomicMethodCallAnalyzer.php | 36 ++++++++++++++++++ .../StaticMethod/AtomicStaticCallAnalyzer.php | 34 +++++++++++++++++ tests/ClosureTest.php | 38 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php index 7236562df6d..6183fe74699 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php @@ -27,6 +27,7 @@ use Psalm\Storage\ClassLikeStorage; use Psalm\Type; use Psalm\Type\Atomic; +use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TFalse; @@ -188,6 +189,41 @@ public static function analyze( ); } + if ($stmt->isFirstClassCallable()) { + $return_type_candidate = null; + $method_name_type = $statements_analyzer->node_data->getType($stmt->name); + if ($method_name_type && $method_name_type->isSingleStringLiteral()) { + $method_identifier = new MethodIdentifier( + $fq_class_name, + strtolower($method_name_type->getSingleStringLiteral()->value) + ); + //the call to methodExists will register that the method was called from somewhere + if ($codebase->methods->methodExists( + $method_identifier, + $context->calling_method_id, + null, + $statements_analyzer, + $statements_analyzer->getFilePath(), + true, + $context->insideUse() + )) { + $method_storage = $codebase->methods->getStorage($method_identifier); + + $return_type_candidate = new Union([new TClosure( + 'Closure', + $method_storage->params, + $method_storage->return_type, + $method_storage->pure + )]); + } + } + + $statements_analyzer->node_data->setType($stmt, $return_type_candidate ?? Type::getClosure()); + + + return; + } + ArgumentsAnalyzer::analyze( $statements_analyzer, $stmt->getArgs(), diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index 0130f030696..cd77bffcf5c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -225,6 +225,40 @@ public static function analyze( ); } + if ($stmt->isFirstClassCallable()) { + $return_type_candidate = null; + $method_name_type = $statements_analyzer->node_data->getType($stmt->name); + if ($method_name_type && $method_name_type->isSingleStringLiteral()) { + $method_identifier = new MethodIdentifier( + $fq_class_name, + strtolower($method_name_type->getSingleStringLiteral()->value) + ); + //the call to methodExists will register that the method was called from somewhere + if ($codebase->methods->methodExists( + $method_identifier, + $context->calling_method_id, + null, + $statements_analyzer, + $statements_analyzer->getFilePath(), + true, + $context->insideUse() + )) { + $method_storage = $codebase->methods->getStorage($method_identifier); + + $return_type_candidate = new Union([new TClosure( + 'Closure', + $method_storage->params, + $method_storage->return_type, + $method_storage->pure + )]); + } + } + + $statements_analyzer->node_data->setType($stmt, $return_type_candidate ?? Type::getClosure()); + + return; + } + if (ArgumentsAnalyzer::analyze( $statements_analyzer, $stmt->getArgs(), diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index 825f5e8999c..a4558a08801 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -609,6 +609,27 @@ public function length(): int { [], '8.1' ], + 'FirstClassCallable:InstanceMethod:Expr' => [ + 'string); + } + } + $test = new Test("test"); + $method_name = "length"; + $closure = $test->$method_name(...); + $length = $closure(); + ', + 'assertions' => [ + '$length' => 'int', + ], + [], + '8.1' + ], 'FirstClassCallable:InstanceMethod:BuiltIn' => [ ' [ + ' [ + '$length' => 'int', + ], + [], + '8.1' + ], 'FirstClassCallable:InvokableObject' => [ ' Date: Sat, 22 Jan 2022 17:45:40 +0100 Subject: [PATCH 4/7] fix a weird case --- .../StaticMethod/AtomicStaticCallAnalyzer.php | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index cd77bffcf5c..a7711f55e35 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -227,30 +227,32 @@ public static function analyze( if ($stmt->isFirstClassCallable()) { $return_type_candidate = null; - $method_name_type = $statements_analyzer->node_data->getType($stmt->name); - if ($method_name_type && $method_name_type->isSingleStringLiteral()) { - $method_identifier = new MethodIdentifier( - $fq_class_name, - strtolower($method_name_type->getSingleStringLiteral()->value) - ); - //the call to methodExists will register that the method was called from somewhere - if ($codebase->methods->methodExists( - $method_identifier, - $context->calling_method_id, - null, - $statements_analyzer, - $statements_analyzer->getFilePath(), - true, - $context->insideUse() - )) { - $method_storage = $codebase->methods->getStorage($method_identifier); - - $return_type_candidate = new Union([new TClosure( - 'Closure', - $method_storage->params, - $method_storage->return_type, - $method_storage->pure - )]); + if (!$stmt->name instanceof PhpParser\Node\Identifier) { + $method_name_type = $statements_analyzer->node_data->getType($stmt->name); + if ($method_name_type && $method_name_type->isSingleStringLiteral()) { + $method_identifier = new MethodIdentifier( + $fq_class_name, + strtolower($method_name_type->getSingleStringLiteral()->value) + ); + //the call to methodExists will register that the method was called from somewhere + if ($codebase->methods->methodExists( + $method_identifier, + $context->calling_method_id, + null, + $statements_analyzer, + $statements_analyzer->getFilePath(), + true, + $context->insideUse() + )) { + $method_storage = $codebase->methods->getStorage($method_identifier); + + $return_type_candidate = new Union([new TClosure( + 'Closure', + $method_storage->params, + $method_storage->return_type, + $method_storage->pure + )]); + } } } From 01868a9766c8b1207d4f8f3ab3bcfb0425146884 Mon Sep 17 00:00:00 2001 From: AndrolGenhald Date: Sat, 22 Jan 2022 16:03:35 -0600 Subject: [PATCH 5/7] Mark loadXdebugStub as deprecated (removed in #7107). --- config.xsd | 5 +++++ psalm-baseline.xml | 9 ++++++++- src/Psalm/Config.php | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config.xsd b/config.xsd index 7d076a14b88..01115776b3d 100644 --- a/config.xsd +++ b/config.xsd @@ -85,6 +85,11 @@ Default is runtime-specific: if not present, Psalm will only load the Xdebug stub if psalm has unloaded the extension. + + + + Deprecated. In Psalm 5 extensions will be loaded based on composer.json and overridden with enableExtensions/disableExtensions. + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1a2dc6c1b8e..bd1946230cc 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -26,13 +26,14 @@ - + $codebase->php_major_version $codebase->php_major_version $codebase->php_major_version $codebase->php_major_version $codebase->php_minor_version $codebase->php_minor_version + $this->load_xdebug_stub getAdditionalFileExtensions @@ -386,6 +387,12 @@ $stmt->expr->getArgs()[0] + + + $config->load_xdebug_stub + $config->load_xdebug_stub + + new TEmpty() diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 6392d72d449..3cf99bb8bfb 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -195,6 +195,8 @@ class Config /** * Whether or not to load Xdebug stub * + * @deprecated going to be removed in Psalm 5 + * * @var bool|null */ public $load_xdebug_stub; From 82d84b0b3aec2ce94d9f8ad020921b459a9d4874 Mon Sep 17 00:00:00 2001 From: orklah Date: Sun, 23 Jan 2022 12:55:35 +0100 Subject: [PATCH 6/7] fix internal properties on interfaces --- .../Expression/Fetch/AtomicPropertyFetchAnalyzer.php | 5 ++++- tests/EnumTest.php | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php index b0961eec718..b0ffb87d273 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php @@ -1007,7 +1007,10 @@ private static function handleNonExistentClass( } } - if (!$class_exists) { + if (!$class_exists && + //interfaces can't have properties. Except when they do... In PHP Core, they can + !in_array($fq_class_name, ['UnitEnum', 'BackedEnum'], true) + ) { if (IssueBuffer::accepts( new NoInterfaceProperties( 'Interfaces cannot have properties', diff --git a/tests/EnumTest.php b/tests/EnumTest.php index 38b58bc204a..9cfe0c93861 100644 --- a/tests/EnumTest.php +++ b/tests/EnumTest.php @@ -371,6 +371,17 @@ enum Status: int { [], '8.1', ], + 'InterfacesWithProperties' => [ + ' $tag->name; + + static fn (\BackedEnum $tag): string|int => $tag->value; + ', + 'assertions' => [], + [], + '8.1', + ], ]; } From 3649712c666b273bfdd9fd5676bb4f5af871db1f Mon Sep 17 00:00:00 2001 From: orklah Date: Sun, 23 Jan 2022 23:26:24 +0100 Subject: [PATCH 7/7] deprecate TPositiveInt --- psalm-baseline.xml | 73 +++++++++++++++++++------- src/Psalm/Type.php | 1 + src/Psalm/Type/Atomic/TPositiveInt.php | 1 + 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index bd1946230cc..b04ad4de319 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -21,11 +21,13 @@ $matches[0] $symbol_parts[1] - - $analysis_php_version_id - + + getAdditionalFileExtensions + getAdditionalFileTypeAnalyzers + getAdditionalFileTypeScanners + $codebase->php_major_version $codebase->php_major_version @@ -35,11 +37,6 @@ $codebase->php_minor_version $this->load_xdebug_stub - - getAdditionalFileExtensions - getAdditionalFileTypeScanners - getAdditionalFileTypeAnalyzers - @@ -205,17 +202,26 @@ - + + new TPositiveInt() + + Type::getEmpty() Type::getEmpty() Type::getEmpty() Type::getEmpty() + Type::getPositiveInt(true) $invalid_left_messages[0] $invalid_right_messages[0] + + + Type::getPositiveInt() + + $codebase->php_major_version @@ -256,6 +262,11 @@ + + new TPositiveInt + new TPositiveInt + new TPositiveInt + Type::getEmpty() @@ -337,6 +348,11 @@ Type::getEmpty() + + + Type::getPositiveInt() + + $invalid_fetch_types[0] @@ -567,10 +583,21 @@ $codebase->php_major_version + + + TPositiveInt::class + + + + + TPositiveInt::class + + - + new TEmpty() new TEmpty() + new TPositiveInt() Type::getEmpty() @@ -601,6 +628,9 @@ + + new TPositiveInt() + $combination->array_type_params[1] $combination->array_type_params[1] @@ -611,6 +641,9 @@ + + new TPositiveInt() + $intersection_types[0] $parse_tree->children[0] @@ -623,12 +656,6 @@ array_keys($template_type_map[$template_param_name])[0] - - - addFileExtension - addFileExtension - - VirtualClass @@ -654,22 +681,30 @@ VirtualConst + + + addFileExtension + addFileExtension + + $rules[0] - + new TEmpty new TEmpty new TEmpty() + new TPositiveInt() - + new TEmpty() new THtmlEscapedString() + new TPositiveInt() array_keys($template_type_map[$value])[0] diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 6d0c9e0fb45..35ca811372e 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -196,6 +196,7 @@ public static function getLowercaseString(): Union return new Union([$type]); } + /** @deprecated will be removed in Psalm 5 */ public static function getPositiveInt(bool $from_calculation = false): Union { $union = new Union([new TPositiveInt()]); diff --git a/src/Psalm/Type/Atomic/TPositiveInt.php b/src/Psalm/Type/Atomic/TPositiveInt.php index eb345a0ba1a..4e45ef06e32 100644 --- a/src/Psalm/Type/Atomic/TPositiveInt.php +++ b/src/Psalm/Type/Atomic/TPositiveInt.php @@ -4,6 +4,7 @@ /** * Denotes an int that is also positive (strictly > 0) + * @deprecated will be removed in Psalm 5 */ class TPositiveInt extends TInt {