Skip to content

Commit 4acbc6c

Browse files
committed
Calling parent:: preserves generic types
1 parent a1b7b38 commit 4acbc6c

13 files changed

+85
-26
lines changed

src/Analyser/MutatingScope.php

+23-5
Original file line numberDiff line numberDiff line change
@@ -854,8 +854,7 @@ private function resolveType(Expr $node): Type
854854
$uncertainty = false;
855855

856856
if ($node->class instanceof Node\Name) {
857-
$className = $this->resolveName($node->class);
858-
$classType = new ObjectType($className);
857+
$classType = $this->resolveTypeByName($node->class);
859858
} else {
860859
$classType = $this->getType($node->class);
861860
$classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type {
@@ -1771,7 +1770,7 @@ private function resolveType(Expr $node): Type
17711770
if ($resolvedName === 'parent' && strtolower($constantName) === 'class') {
17721771
return new ClassStringType();
17731772
}
1774-
$constantClassType = new ObjectType($resolvedName);
1773+
$constantClassType = $this->resolveTypeByName($node->class);
17751774
}
17761775

17771776
if (strtolower($constantName) === 'class') {
@@ -1932,7 +1931,7 @@ private function resolveType(Expr $node): Type
19321931
if ($node instanceof Expr\StaticCall && $node->name instanceof Node\Identifier) {
19331932
$typeCallback = function () use ($node): Type {
19341933
if ($node->class instanceof Name) {
1935-
$staticMethodCalledOnType = new ObjectType($this->resolveName($node->class));
1934+
$staticMethodCalledOnType = $this->resolveTypeByName($node->class);
19361935
} else {
19371936
$staticMethodCalledOnType = $this->getType($node->class);
19381937
if ($staticMethodCalledOnType instanceof GenericClassStringType) {
@@ -2045,7 +2044,7 @@ private function resolveType(Expr $node): Type
20452044
) {
20462045
$typeCallback = function () use ($node): Type {
20472046
if ($node->class instanceof Name) {
2048-
$staticPropertyFetchedOnType = new ObjectType($this->resolveName($node->class));
2047+
$staticPropertyFetchedOnType = $this->resolveTypeByName($node->class);
20492048
} else {
20502049
$staticPropertyFetchedOnType = $this->getType($node->class);
20512050
if ($staticPropertyFetchedOnType instanceof GenericClassStringType) {
@@ -2448,6 +2447,25 @@ public function resolveName(Name $name): string
24482447
return $originalClass;
24492448
}
24502449

2450+
public function resolveTypeByName(Name $name): TypeWithClassName
2451+
{
2452+
if ($name->toLowerString() === 'static' && $this->isInClass()) {
2453+
$classReflection = $this->getClassReflection();
2454+
2455+
return new StaticType($classReflection->getName());
2456+
}
2457+
$originalClass = $this->resolveName($name);
2458+
if ($this->isInClass()) {
2459+
$thisType = new ThisType($this->getClassReflection());
2460+
$ancestor = $thisType->getAncestorWithClassName($originalClass);
2461+
if ($ancestor !== null) {
2462+
return $ancestor;
2463+
}
2464+
}
2465+
2466+
return new ObjectType($originalClass);
2467+
}
2468+
24512469
/**
24522470
* @param mixed $value
24532471
*/

src/Analyser/NodeScopeResolver.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,7 @@ private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr
14381438
$methodCalledOnType = $scope->getType($expr->var);
14391439
} else {
14401440
if ($expr->class instanceof Name) {
1441-
$methodCalledOnType = new ObjectType($scope->resolveName($expr->class));
1441+
$methodCalledOnType = $scope->resolveTypeByName($expr->class);
14421442
} else {
14431443
$methodCalledOnType = $scope->getType($expr->class);
14441444
}
@@ -2770,7 +2770,7 @@ private function processAssignVar(
27702770

27712771
} elseif ($var instanceof Expr\StaticPropertyFetch) {
27722772
if ($var->class instanceof \PhpParser\Node\Name) {
2773-
$propertyHolderType = new ObjectType($scope->resolveName($var->class));
2773+
$propertyHolderType = $scope->resolveTypeByName($var->class);
27742774
} else {
27752775
$this->processExprNode($var->class, $scope, $nodeCallback, $context);
27762776
$propertyHolderType = $scope->getType($var->class);

src/Analyser/Scope.php

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public function doNotTreatPhpDocTypesAsCertain(): self;
7070

7171
public function resolveName(Name $name): string;
7272

73+
public function resolveTypeByName(Name $name): Type;
74+
7375
/**
7476
* @param mixed $value
7577
*/

src/Analyser/TypeSpecifier.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ public function specifyTypesInCondition(
549549
}
550550
} elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) {
551551
if ($expr->class instanceof Name) {
552-
$calleeType = new ObjectType($scope->resolveName($expr->class));
552+
$calleeType = $scope->resolveTypeByName($expr->class);
553553
} else {
554554
$calleeType = $scope->getType($expr->class);
555555
}

src/Node/ClassPropertiesNode.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ private function getMethodsCalledFromConstructor(
213213
if (!$methodCallNode->class instanceof Name) {
214214
continue;
215215
}
216-
$calledOnType = new ObjectType($callScope->resolveName($methodCallNode->class));
216+
217+
$calledOnType = $callScope->resolveTypeByName($methodCallNode->class);
217218
}
218219
if ($classType->isSuperTypeOf($calledOnType)->no()) {
219220
continue;

src/Rules/Classes/ImpossibleInstanceOfRule.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\RuleErrorBuilder;
88
use PHPStan\Type\Constant\ConstantBooleanType;
9-
use PHPStan\Type\ObjectType;
109
use PHPStan\Type\ObjectWithoutClassType;
1110
use PHPStan\Type\StringType;
1211
use PHPStan\Type\TypeCombinator;
@@ -42,8 +41,7 @@ public function processNode(Node $node, Scope $scope): array
4241
$expressionType = $scope->getType($node->expr);
4342

4443
if ($node->class instanceof Node\Name) {
45-
$className = $scope->resolveName($node->class);
46-
$classType = new ObjectType($className);
44+
$classType = $scope->resolveTypeByName($node->class);
4745
} else {
4846
$classType = $scope->getType($node->class);
4947
$allowed = TypeCombinator::union(

src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Reflection\MethodReflection;
99
use PHPStan\Rules\RuleErrorBuilder;
10-
use PHPStan\Type\ObjectType;
1110

1211
/**
1312
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\StaticCall>
@@ -102,7 +101,7 @@ private function getMethod(
102101
): MethodReflection
103102
{
104103
if ($class instanceof Node\Name) {
105-
$calledOnType = new ObjectType($scope->resolveName($class));
104+
$calledOnType = $scope->resolveTypeByName($class);
106105
} else {
107106
$calledOnType = $scope->getType($class);
108107
}

src/Rules/DeadCode/UnusedPrivateMethodRule.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function processNode(Node $node, Scope $scope): array
8484
if (!$methodCallNode->class instanceof Node\Name) {
8585
continue;
8686
}
87-
$calledOnType = new ObjectType($callScope->resolveName($methodCallNode->class));
87+
$calledOnType = $scope->resolveTypeByName($methodCallNode->class);
8888
}
8989
if ($classType->isSuperTypeOf($calledOnType)->no()) {
9090
continue;

src/Rules/DeadCode/UnusedPrivatePropertyRule.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public function processNode(Node $node, Scope $scope): array
147147
continue;
148148
}
149149

150-
$fetchedOnType = new ObjectType($usage->getScope()->resolveName($fetch->class));
150+
$fetchedOnType = $usage->getScope()->resolveTypeByName($fetch->class);
151151
}
152152

153153
if ($classType->isSuperTypeOf($fetchedOnType)->no()) {

src/Rules/Properties/PropertyReflectionFinder.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PhpParser\Node\VarLikeIdentifier;
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Type\Constant\ConstantStringType;
10-
use PHPStan\Type\ObjectType;
1110
use PHPStan\Type\StaticType;
1211
use PHPStan\Type\ThisType;
1312
use PHPStan\Type\Type;
@@ -57,7 +56,7 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a
5756
}
5857

5958
if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
60-
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
59+
$propertyHolderType = $scope->resolveTypeByName($propertyFetch->class);
6160
} else {
6261
$propertyHolderType = $scope->getType($propertyFetch->class);
6362
}
@@ -114,7 +113,7 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F
114113
}
115114

116115
if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
117-
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
116+
$propertyHolderType = $scope->resolveTypeByName($propertyFetch->class);
118117
} else {
119118
$propertyHolderType = $scope->getType($propertyFetch->class);
120119
}

src/Type/Php/IsAFunctionTypeSpecifyingExtension.php

+2-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use PHPStan\Type\Generic\GenericClassStringType;
1919
use PHPStan\Type\ObjectType;
2020
use PHPStan\Type\ObjectWithoutClassType;
21-
use PHPStan\Type\StaticType;
2221

2322
class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
2423
{
@@ -47,12 +46,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
4746
&& $classNameArgExpr->name instanceof \PhpParser\Node\Identifier
4847
&& strtolower($classNameArgExpr->name->name) === 'class'
4948
) {
50-
$className = $scope->resolveName($classNameArgExpr->class);
51-
if (strtolower($classNameArgExpr->class->toString()) === 'static') {
52-
$objectType = new StaticType($className);
53-
} else {
54-
$objectType = new ObjectType($className);
55-
}
49+
$objectType = $scope->resolveTypeByName($classNameArgExpr->class);
50+
// todo static => StaticType
5651
$types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context);
5752
} elseif ($classNameArgExprType instanceof ConstantStringType) {
5853
$objectType = new ObjectType($classNameArgExprType->getValue());

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -5651,6 +5651,11 @@ public function dataGenericUnions(): array
56515651
return $this->gatherAssertTypes(__DIR__ . '/data/generic-unions.php');
56525652
}
56535653

5654+
public function dataGenericParent(): array
5655+
{
5656+
return $this->gatherAssertTypes(__DIR__ . '/data/generic-parent.php');
5657+
}
5658+
56545659
/**
56555660
* @dataProvider dataArrayFunctions
56565661
* @param string $description
@@ -11264,6 +11269,7 @@ private function gatherAssertTypes(string $file): array
1126411269
* @dataProvider dataGenericTraits
1126511270
* @dataProvider dataBug4423
1126611271
* @dataProvider dataGenericUnions
11272+
* @dataProvider dataGenericParent
1126711273
* @param string $assertType
1126811274
* @param string $file
1126911275
* @param mixed ...$args
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace GenericParent;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
interface Animal
8+
{
9+
10+
}
11+
12+
interface Dog extends Animal
13+
{
14+
15+
}
16+
17+
/**
18+
* @template T of Animal
19+
*/
20+
class Foo
21+
{
22+
23+
/** @return T */
24+
public function getAnimal(): Animal
25+
{
26+
27+
}
28+
29+
}
30+
31+
/** @extends Foo<Dog> */
32+
class Bar extends Foo
33+
{
34+
35+
public function doFoo()
36+
{
37+
assertType(Dog::class, parent::getAnimal());
38+
assertType(Dog::class, Foo::getAnimal());
39+
}
40+
41+
}

0 commit comments

Comments
 (0)