Skip to content

Commit

Permalink
Merge pull request #7430 from b2pweb/resolve-generics-on-inherited-ps…
Browse files Browse the repository at this point in the history
…eudo-methods

Resolve generics of inherited pseudo methods
  • Loading branch information
orklah committed Jan 19, 2022
2 parents 84a4cbd + f725241 commit d274d52
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 7 deletions.
Expand Up @@ -113,7 +113,7 @@ public static function handleMagicMethod(

$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$defining_class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
Expand Down Expand Up @@ -157,7 +157,7 @@ public static function handleMagicMethod(
$codebase,
$return_type_candidate,
$defining_class_storage->name,
$fq_class_name,
$lhs_type_part instanceof Atomic\TNamedObject ? $lhs_type_part : $fq_class_name,
$defining_class_storage->parent_class
);

Expand Down Expand Up @@ -280,7 +280,7 @@ public static function handleMissingOrMagicMethod(

$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$defining_class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
Expand Down Expand Up @@ -336,7 +336,7 @@ public static function handleMissingOrMagicMethod(
$codebase,
$return_type_candidate,
$defining_class_storage->name,
$fq_class_name,
$lhs_type_part instanceof Atomic\TNamedObject ? $lhs_type_part : $fq_class_name,
$defining_class_storage->parent_class,
true,
false,
Expand Down Expand Up @@ -423,11 +423,20 @@ private static function findPseudoMethodAndClassStorages(
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);
Expand Down
3 changes: 3 additions & 0 deletions src/Psalm/Internal/Codebase/Populator.php
Expand Up @@ -448,6 +448,7 @@ private function populateDataFromTraits(
$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;
}
}

Expand Down Expand Up @@ -606,6 +607,7 @@ function ($constant) {
$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(
Expand Down Expand Up @@ -694,6 +696,7 @@ function ($constant) {
$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);
Expand Down
Expand Up @@ -598,11 +598,16 @@ function (array $l, array $r): int {

/** @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,
$lc_method_name
);
}

$storage->sealed_methods = true;
Expand Down
10 changes: 10 additions & 0 deletions src/Psalm/Storage/ClassLikeStorage.php
Expand Up @@ -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>
*/
Expand Down
86 changes: 86 additions & 0 deletions tests/MagicMethodAnnotationTest.php
Expand Up @@ -819,6 +819,92 @@ function consumeInt(int $i): void {}
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',
]
],
];
}

Expand Down

0 comments on commit d274d52

Please sign in to comment.