Skip to content

Commit

Permalink
Merge pull request vimeo#8370 from someniatko/issue-8363
Browse files Browse the repository at this point in the history
Ensure we recognize inherited static methods for the first-class callables
  • Loading branch information
orklah committed Aug 18, 2022
2 parents 0d0a049 + be02e7e commit 7ee3a81
Show file tree
Hide file tree
Showing 2 changed files with 100 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,53 @@ 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();
}
}

$expanded_return_type = TypeExpander::expandUnion(
$codebase,
$return_type_candidate,
$context->self,
$class_storage->name,
$context->parent,
true,
false,
true
);

$statements_analyzer->node_data->setType($stmt, $expanded_return_type);

return true;
}

if (!$naive_method_exists
|| !MethodAnalyzer::isMethodVisible(
$method_id,
Expand Down
49 changes: 49 additions & 0 deletions tests/ClosureTest.php
Expand Up @@ -751,6 +751,55 @@ public static function __callStatic(string $name, array $args): mixed {
[],
'8.1'
],
'FirstClassCallable:InheritedStaticMethod' => [
'<?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:InheritedStaticMethodWithStaticTypeParameter' => [
'<?php
/** @template T */
class Holder
{
/** @param T $value */
public function __construct(public $value) {}
}
abstract class A
{
final public function __construct(public int $i) {}
/** @return Holder<static> */
public static function create(int $i): Holder
{
return new Holder(new static($i));
}
}
class C extends A {}
/** @param \Closure(int):Holder<C> $_ */
function takesIntToHolder(\Closure $_): void {}
takesIntToHolder(C::create(...));'
],
'FirstClassCallable:WithArrayMap' => [
'<?php
$array = [1, 2, 3];
Expand Down

0 comments on commit 7ee3a81

Please sign in to comment.