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 ad9fd724f4e..8e187ab391f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -30,6 +30,7 @@ use Psalm\Issue\InvalidStringClass; use Psalm\Issue\MixedMethodCall; use Psalm\Issue\UndefinedClass; +use Psalm\Issue\UndefinedMethod; use Psalm\IssueBuffer; use Psalm\Node\Expr\VirtualArray; use Psalm\Node\Expr\VirtualArrayItem; @@ -490,6 +491,7 @@ private static function handleNamedCall( $method_name_lc, ); + if ($stmt->isFirstClassCallable()) { if ($found_method_and_class_storage) { [ $method_storage ] = $found_method_and_class_storage; @@ -515,8 +517,31 @@ private static function handleNamedCall( $codebase->getMethodReturnType($method_id, $fq_class_name), $codebase->methods->getStorage($declaring_method_id)->pure, )]); + } elseif ($codebase->methodExists( + $call_static_method_id = new MethodIdentifier($method_id->fq_class_name, '__callstatic'), + new CodeLocation($statements_analyzer, $stmt), + null, + null, + false, + )) { + $return_type_candidate = new Union([new TClosure( + 'Closure', + null, + $codebase->getMethodReturnType($call_static_method_id, $fq_class_name), + $codebase->methods->getStorage($call_static_method_id)->pure, + )]); } else { - // FIXME: perhaps Psalm should complain about nonexisting method here, or throw a logic exception? + if (IssueBuffer::accepts( + new UndefinedMethod( + 'Method ' . $method_id . ' does not exist', + new CodeLocation($statements_analyzer, $stmt), + (string) $method_id, + ), + $statements_analyzer->getSuppressedIssues(), + )) { + return false; + } + $return_type_candidate = Type::getClosure(); } } diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 49eebbf7546..def613cc537 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -1805,6 +1805,22 @@ public function foo(callable $_a = "strlen"): void {} PHP, 'error_message' => 'TooManyArguments', ], + 'firstClassCallableWithUnknownStaticMethod' => [ + 'code' => <<<'PHP' + 'UndefinedMethod', + ], + 'firstClassCallableWithUnknownInstanceMethod' => [ + 'code' => <<<'PHP' + foo(...); + PHP, + 'error_message' => 'UndefinedMethod', + ], ]; } }