From 426cb11e577642180417bbb8563f11c3a6bb776e Mon Sep 17 00:00:00 2001 From: someniatko Date: Thu, 4 Aug 2022 16:50:38 +0300 Subject: [PATCH] #8363 - ensure we recognize inherited static methods for the first-class callables --- .../StaticMethod/AtomicStaticCallAnalyzer.php | 71 ++++++++++++------- tests/ClosureTest.php | 21 ++++++ 2 files changed, 65 insertions(+), 27 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 673dc56c505..24838b5b60f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -56,6 +56,7 @@ use function array_filter; use function array_map; +use function array_values; use function assert; use function count; use function in_array; @@ -369,39 +370,15 @@ private static function handleNamedCall( if (!$naive_method_exists && $codebase->methods->existence_provider->has($fq_class_name) ) { - $method_exists = $codebase->methods->existence_provider->doesMethodExist( + $fake_method_exists = $codebase->methods->existence_provider->doesMethodExist( $fq_class_name, $method_id->method_name, $statements_analyzer, null - ); - - if ($method_exists) { - $fake_method_exists = true; - } + ) ?? false; } - if ($stmt->isFirstClassCallable()) { - $method_storage = ($class_storage->methods[$method_name_lc] ?? - ($class_storage->pseudo_static_methods[$method_name_lc] ?? null)); - - if ($method_storage) { - $return_type_candidate = new Union([new TClosure( - 'Closure', - $method_storage->params, - $method_storage->return_type, - $method_storage->pure - )]); - } else { - $return_type_candidate = Type::getClosure(); - } - - $statements_analyzer->node_data->setType($stmt, $return_type_candidate); - - return true; - } - - $args = $stmt->getArgs(); + $args = $stmt->isFirstClassCallable() ? [] : $stmt->getArgs(); if (!$naive_method_exists && $class_storage->mixin_declaring_fqcln @@ -504,6 +481,42 @@ private static function handleNamedCall( $method_name_lc ); + if ($stmt->isFirstClassCallable()) { + if ($found_method_and_class_storage) { + [ $method_storage ] = $found_method_and_class_storage; + + $return_type_candidate = new Union([new TClosure( + 'Closure', + $method_storage->params, + $method_storage->return_type, + $method_storage->pure + )]); + } else { + $method_exists = $naive_method_exists + || $fake_method_exists + || isset($class_storage->methods[$method_name_lc]) + || isset($class_storage->pseudo_static_methods[$method_name_lc]); + + if ($method_exists) { + $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id) ?? $method_id; + + $return_type_candidate = new Union([new TClosure( + 'Closure', + array_values($codebase->getMethodParams($method_id)), + $codebase->getMethodReturnType($method_id, $fq_class_name), + $codebase->methods->getStorage($declaring_method_id)->pure + )]); + } else { + // FIXME: perhaps Psalm should complain about nonexisting method here, or throw a logic exception? + $return_type_candidate = Type::getClosure(); + } + } + + $statements_analyzer->node_data->setType($stmt, $return_type_candidate); + + return true; + } + if (!$naive_method_exists || !MethodAnalyzer::isMethodVisible( $method_id, @@ -879,6 +892,10 @@ private static function checkPseudoMethod( MethodStorage $pseudo_method_storage, Context $context ): ?bool { + if ($stmt->isFirstClassCallable()) { + return null; + } + if (ArgumentsAnalyzer::analyze( $statements_analyzer, $args, diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index cea843a3725..e6e5699600e 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -751,6 +751,27 @@ public static function __callStatic(string $name, array $args): mixed { [], '8.1' ], + 'FirstClassCallable:OverriddenStaticMethod' => [ + ' [], + [], + '8.1', + ], 'FirstClassCallable:WithArrayMap' => [ '