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:
parent
a0f52eae2f
commit
7b25ca75f4
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
*/
|
||||
|
@ -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',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user