From 626ec3592e14d1b39c31b6f9c110bc4410ca81e4 Mon Sep 17 00:00:00 2001 From: robchett Date: Wed, 8 Nov 2023 11:41:42 +0000 Subject: [PATCH 1/3] Inherit conditional returns Fixes #3593 --- .../Method/MethodCallReturnTypeFetcher.php | 6 +- .../ExistingAtomicStaticCallAnalyzer.php | 6 +- .../Reflector/FunctionLikeDocblockScanner.php | 7 +-- tests/Template/ConditionalReturnTypeTest.php | 58 +++++++++++++++++++ 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php index a04104107b2..3d3b39e5b37 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -577,7 +577,7 @@ public static function replaceTemplateTypes( ) { if ($template_type->param_name === 'TFunctionArgCount') { $template_result->lower_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, $arg_count), ), @@ -585,7 +585,7 @@ public static function replaceTemplateTypes( ]; } elseif ($template_type->param_name === 'TPhpMajorVersion') { $template_result->lower_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()), ), @@ -593,7 +593,7 @@ public static function replaceTemplateTypes( ]; } elseif ($template_type->param_name === 'TPhpVersionId') { $template_result->lower_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt( false, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index c87fa0f8462..af7a26fafae 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -629,7 +629,7 @@ private static function resolveTemplateResultLowerBound( ): array { if ($template_type->param_name === 'TFunctionArgCount') { return [ - 'fn-' . strtolower((string)$method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, count($stmt->getArgs())), ), @@ -639,7 +639,7 @@ private static function resolveTemplateResultLowerBound( if ($template_type->param_name === 'TPhpMajorVersion') { return [ - 'fn-' . strtolower((string)$method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()), ), @@ -649,7 +649,7 @@ private static function resolveTemplateResultLowerBound( if ($template_type->param_name === 'TPhpVersionId') { return [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt( false, diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index 0d9bc183cba..b7808ee3879 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -512,9 +512,8 @@ private static function getConditionalSanitizedTypeTokens( if ($token_body === 'func_num_args()') { $template_name = 'TFunctionArgCount'; - $storage->template_types[$template_name] = [ - $template_function_id => Type::getInt(), + 'fn-' . strtolower($storage->cased_name ?? '') => Type::getInt(), ]; $function_template_types[$template_name] @@ -527,7 +526,7 @@ private static function getConditionalSanitizedTypeTokens( $template_name = 'TPhpMajorVersion'; $storage->template_types[$template_name] = [ - $template_function_id => Type::getInt(), + 'fn-' . strtolower($storage->cased_name ?? '') => Type::getInt(), ]; $function_template_types[$template_name] @@ -540,7 +539,7 @@ private static function getConditionalSanitizedTypeTokens( $template_name = 'TPhpVersionId'; $storage->template_types[$template_name] = [ - $template_function_id => Type::getInt(), + 'fn-' . strtolower($storage->cased_name ?? '') => Type::getInt(), ]; $function_template_types[$template_name] diff --git a/tests/Template/ConditionalReturnTypeTest.php b/tests/Template/ConditionalReturnTypeTest.php index 5bf824e9b23..7698f3ce966 100644 --- a/tests/Template/ConditionalReturnTypeTest.php +++ b/tests/Template/ConditionalReturnTypeTest.php @@ -463,6 +463,31 @@ function zeroArgsFalseOneArgString(string $s = "") { '$c' => 'string', ], ], + 'InheritFuncNumArgs' => [ + 'code' => ' [ 'code' => ' [], 'php_version' => '7.2', ], + 'ineritedreturnTypeBasedOnPhpVersionId' => [ + 'code' => ' ? string : int) + */ + function getSomething() + { + return mt_rand(1, 10) > 5 ? "a value" : 42; + } + + /** + * @psalm-return (PHP_VERSION_ID is int<70100, max> ? string : int) + */ + function getSomethingElse() + { + return mt_rand(1, 10) > 5 ? "a value" : 42; + } + } + + class B extends A {} + + $class = new B(); + $something = $class->getSomething(); + $somethingElse = $class->getSomethingElse(); + ', + 'assertions' => [ + '$something' => 'int', + '$somethingElse' => 'string', + ], + 'ignored_issues' => [], + 'php_version' => '7.2', + ], 'ineritedConditionalTemplatedReturnType' => [ 'code' => ' Date: Wed, 29 Nov 2023 13:32:45 +0100 Subject: [PATCH 2/3] Fix parsing of array keys with @ --- src/Psalm/Internal/Type/ParseTreeCreator.php | 6 +++++- tests/TypeAnnotationTest.php | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/ParseTreeCreator.php b/src/Psalm/Internal/Type/ParseTreeCreator.php index 737b5ac3f1b..587d2fcd842 100644 --- a/src/Psalm/Internal/Type/ParseTreeCreator.php +++ b/src/Psalm/Internal/Type/ParseTreeCreator.php @@ -830,7 +830,11 @@ private function handleValue(array $type_token): void $nexter_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; - if ($nexter_token && strpos($nexter_token[0], '@') !== false) { + if ($nexter_token + && strpos($nexter_token[0], '@') !== false + && $type_token[0] !== 'list' + && $type_token[0] !== 'array' + ) { $this->t = $this->type_token_count; if ($type_token[0] === '$this') { $type_token[0] = 'static'; diff --git a/tests/TypeAnnotationTest.php b/tests/TypeAnnotationTest.php index 07058f21998..bdec0ba36bf 100644 --- a/tests/TypeAnnotationTest.php +++ b/tests/TypeAnnotationTest.php @@ -15,6 +15,14 @@ class TypeAnnotationTest extends TestCase public function providerValidCodeParse(): iterable { return [ + 'atInArrayKey' => [ + 'code' => ' $v + */ + function a(array $v): void {}', + ], 'typeAliasBeforeClass' => [ 'code' => ' Date: Fri, 8 Dec 2023 12:31:42 +0100 Subject: [PATCH 3/3] Fix iteration over weakmaps --- stubs/CoreGenericClasses.phpstub | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/stubs/CoreGenericClasses.phpstub b/stubs/CoreGenericClasses.phpstub index 2b7b76da7e4..c93c1d8f01e 100644 --- a/stubs/CoreGenericClasses.phpstub +++ b/stubs/CoreGenericClasses.phpstub @@ -492,6 +492,16 @@ final class WeakMap implements ArrayAccess, Countable, IteratorAggregate, Traver * @return void */ public function offsetUnset($offset) {} + + /** + * Create a new iterator from an ArrayObject instance + * @link http://php.net/manual/en/arrayobject.getiterator.php + * + * @return \Traversable An iterator from an ArrayObject. + * + * @since 5.0.0 + */ + public function getIterator() { } } class mysqli