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 96b2f8b2a8c..88646faa5b6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -630,7 +630,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())), ), @@ -640,7 +640,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()), ), @@ -650,7 +650,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 a284d86a6e3..7afd240275b 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -515,9 +515,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] @@ -530,7 +529,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] @@ -543,7 +542,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/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 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' => '