1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Resolve generics of inherited pseudo methods (fix #7419)

This commit is contained in:
Vincent 2022-01-19 11:45:19 +01:00
parent a0f52eae2f
commit 7b25ca75f4
5 changed files with 120 additions and 7 deletions

View File

@ -113,7 +113,7 @@ class MissingMethodCallHandler
$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$defining_class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
@ -157,7 +157,7 @@ class MissingMethodCallHandler
$codebase,
$return_type_candidate,
$defining_class_storage->name,
$fq_class_name,
$lhs_type_part,
$defining_class_storage->parent_class
);
@ -280,7 +280,7 @@ class MissingMethodCallHandler
$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$defining_class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
@ -336,7 +336,7 @@ class MissingMethodCallHandler
$codebase,
$return_type_candidate,
$defining_class_storage->name,
$fq_class_name,
$lhs_type_part,
$defining_class_storage->parent_class,
true,
false,
@ -423,11 +423,20 @@ class MissingMethodCallHandler
ClassLikeStorage $static_class_storage,
string $method_name_lc
): ?array {
if (isset($static_class_storage->declaring_pseudo_method_ids[$method_name_lc])) {
$method_id = $static_class_storage->declaring_pseudo_method_ids[$method_name_lc];
$class_storage = $codebase->classlikes->getStorageFor($method_id->fq_class_name);
if ($class_storage && isset($class_storage->pseudo_methods[$method_name_lc])) {
return [$class_storage->pseudo_methods[$method_name_lc], $class_storage];
}
}
if ($pseudo_method_storage = $static_class_storage->pseudo_methods[$method_name_lc] ?? null) {
return [$pseudo_method_storage, $static_class_storage];
}
$ancestors = $static_class_storage->class_implements + $static_class_storage->parent_classes;
$ancestors = $static_class_storage->class_implements;
foreach ($ancestors as $fq_class_name => $_) {
$class_storage = $codebase->classlikes->getStorageFor($fq_class_name);

View File

@ -448,6 +448,7 @@ class Populator
$storage->pseudo_property_set_types += $trait_storage->pseudo_property_set_types;
$storage->pseudo_methods += $trait_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $trait_storage->declaring_pseudo_method_ids;
}
}
@ -606,6 +607,7 @@ class Populator
$parent_storage->dependent_classlikes[strtolower($storage->name)] = true;
$storage->pseudo_methods += $parent_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $parent_storage->declaring_pseudo_method_ids;
}
private function populateInterfaceDataFromParentInterfaces(
@ -694,6 +696,7 @@ class Populator
$this->inheritMethodsFromParent($storage, $parent_interface_storage);
$storage->pseudo_methods += $parent_interface_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $parent_interface_storage->declaring_pseudo_method_ids;
}
$storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces);

View File

@ -598,11 +598,16 @@ class ClassLikeNodeScanner
/** @var MethodStorage */
$pseudo_method_storage = $functionlike_node_scanner->start($method, true);
$lc_method_name = strtolower($method->name->name);
if ($pseudo_method_storage->is_static) {
$storage->pseudo_static_methods[strtolower($method->name->name)] = $pseudo_method_storage;
$storage->pseudo_static_methods[$lc_method_name] = $pseudo_method_storage;
} else {
$storage->pseudo_methods[strtolower($method->name->name)] = $pseudo_method_storage;
$storage->pseudo_methods[$lc_method_name] = $pseudo_method_storage;
$storage->declaring_pseudo_method_ids[$lc_method_name] = new MethodIdentifier(
$fq_classlike_name,
$method->name->name
);
}
$storage->sealed_methods = true;

View File

@ -236,6 +236,16 @@ class ClassLikeStorage
*/
public $pseudo_static_methods = [];
/**
* Maps pseudo method names to the original declaring method identifier
* The key is the method name in lowercase, and the value is the original `MethodIdentifier` instance
*
* This property contains all pseudo methods declared on ancestors.
*
* @var array<lowercase-string, MethodIdentifier>
*/
public $declaring_pseudo_method_ids = [];
/**
* @var array<lowercase-string, MethodIdentifier>
*/

View File

@ -819,6 +819,92 @@ class MagicMethodAnnotationTest extends TestCase
consumeInt(B::bar());'
],
'returnThisShouldKeepGenerics' => [
'<?php
/**
* @template E
* @method $this foo()
*/
class A
{
public function __call(string $name, array $args) {}
}
/**
* @template E
* @method $this foo()
*/
interface I {}
class B {}
/** @var A<B> $a */
$a = new A();
$b = $a->foo();
/** @var I<B> $i */
$c = $i->foo();',
[
'$b' => 'A<B>',
'$c' => 'I<B>',
]
],
'genericsOfInheritedMethodsShouldBeResolved' => [
'<?php
/**
* @template E
* @method E get()
*/
interface I {}
/**
* @template E
* @implements I<E>
*/
class A implements I
{
public function __call(string $name, array $args) {}
}
/**
* @template E
* @extends I<E>
*/
interface I2 extends I {}
class B {}
/**
* @template E
* @method E get()
*/
class C
{
public function __call(string $name, array $args) {}
}
/**
* @template E
* @extends C<E>
*/
class D extends C {}
/** @var A<B> $a */
$a = new A();
$b = $a->get();
/** @var I2<B> $i */
$c = $i->get();
/** @var D<B> $d */
$d = new D();
$e = $d->get();',
[
'$b' => 'B',
'$c' => 'B',
'$e' => 'B',
]
],
];
}