Skip to content

Commit

Permalink
#8363 - ensure we recognize inherited static methods for the first-cl…
Browse files Browse the repository at this point in the history
…ass callables
  • Loading branch information
someniatko committed Aug 4, 2022
1 parent 1ef3851 commit f3d6784
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 27 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

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;
) ?? false;
}

$args = $stmt->getArgs();
$args = $stmt->isFirstClassCallable() ? [] : $stmt->getArgs();

if (!$naive_method_exists
&& $class_storage->mixin_declaring_fqcln
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions tests/ClosureTest.php
Expand Up @@ -751,6 +751,27 @@ public static function __callStatic(string $name, array $args): mixed {
[],
'8.1'
],
'FirstClassCallable:OverriddenStaticMethod' => [
'<?php
abstract class A
{
public function foo(int $i): string
{
return (string) $i;
}
}
class C extends A {}
/** @param \Closure(int):string $_ */
function takesIntToString(\Closure $_): void {}
takesIntToString(C::foo(...));',
'assertions' => [],
[],
'8.1',
],
'FirstClassCallable:WithArrayMap' => [
'<?php
$array = [1, 2, 3];
Expand Down

0 comments on commit f3d6784

Please sign in to comment.