Skip to content

Commit

Permalink
Calling parent:: preserves generic types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 12, 2021
1 parent a1b7b38 commit 4acbc6c
Show file tree
Hide file tree
Showing 13 changed files with 85 additions and 26 deletions.
28 changes: 23 additions & 5 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -854,8 +854,7 @@ private function resolveType(Expr $node): Type
$uncertainty = false;

if ($node->class instanceof Node\Name) {
$className = $this->resolveName($node->class);
$classType = new ObjectType($className);
$classType = $this->resolveTypeByName($node->class);
} else {
$classType = $this->getType($node->class);
$classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type {
Expand Down Expand Up @@ -1771,7 +1770,7 @@ private function resolveType(Expr $node): Type
if ($resolvedName === 'parent' && strtolower($constantName) === 'class') {
return new ClassStringType();
}
$constantClassType = new ObjectType($resolvedName);
$constantClassType = $this->resolveTypeByName($node->class);
}

if (strtolower($constantName) === 'class') {
Expand Down Expand Up @@ -1932,7 +1931,7 @@ private function resolveType(Expr $node): Type
if ($node instanceof Expr\StaticCall && $node->name instanceof Node\Identifier) {
$typeCallback = function () use ($node): Type {
if ($node->class instanceof Name) {
$staticMethodCalledOnType = new ObjectType($this->resolveName($node->class));
$staticMethodCalledOnType = $this->resolveTypeByName($node->class);
} else {
$staticMethodCalledOnType = $this->getType($node->class);
if ($staticMethodCalledOnType instanceof GenericClassStringType) {
Expand Down Expand Up @@ -2045,7 +2044,7 @@ private function resolveType(Expr $node): Type
) {
$typeCallback = function () use ($node): Type {
if ($node->class instanceof Name) {
$staticPropertyFetchedOnType = new ObjectType($this->resolveName($node->class));
$staticPropertyFetchedOnType = $this->resolveTypeByName($node->class);
} else {
$staticPropertyFetchedOnType = $this->getType($node->class);
if ($staticPropertyFetchedOnType instanceof GenericClassStringType) {
Expand Down Expand Up @@ -2448,6 +2447,25 @@ public function resolveName(Name $name): string
return $originalClass;
}

public function resolveTypeByName(Name $name): TypeWithClassName
{
if ($name->toLowerString() === 'static' && $this->isInClass()) {
$classReflection = $this->getClassReflection();

return new StaticType($classReflection->getName());
}
$originalClass = $this->resolveName($name);
if ($this->isInClass()) {
$thisType = new ThisType($this->getClassReflection());
$ancestor = $thisType->getAncestorWithClassName($originalClass);
if ($ancestor !== null) {
return $ancestor;
}
}

return new ObjectType($originalClass);
}

/**
* @param mixed $value
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,7 @@ private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr
$methodCalledOnType = $scope->getType($expr->var);
} else {
if ($expr->class instanceof Name) {
$methodCalledOnType = new ObjectType($scope->resolveName($expr->class));
$methodCalledOnType = $scope->resolveTypeByName($expr->class);
} else {
$methodCalledOnType = $scope->getType($expr->class);
}
Expand Down Expand Up @@ -2770,7 +2770,7 @@ private function processAssignVar(

} elseif ($var instanceof Expr\StaticPropertyFetch) {
if ($var->class instanceof \PhpParser\Node\Name) {
$propertyHolderType = new ObjectType($scope->resolveName($var->class));
$propertyHolderType = $scope->resolveTypeByName($var->class);
} else {
$this->processExprNode($var->class, $scope, $nodeCallback, $context);
$propertyHolderType = $scope->getType($var->class);
Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public function doNotTreatPhpDocTypesAsCertain(): self;

public function resolveName(Name $name): string;

public function resolveTypeByName(Name $name): Type;

/**
* @param mixed $value
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ public function specifyTypesInCondition(
}
} elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) {
if ($expr->class instanceof Name) {
$calleeType = new ObjectType($scope->resolveName($expr->class));
$calleeType = $scope->resolveTypeByName($expr->class);
} else {
$calleeType = $scope->getType($expr->class);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Node/ClassPropertiesNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ private function getMethodsCalledFromConstructor(
if (!$methodCallNode->class instanceof Name) {
continue;
}
$calledOnType = new ObjectType($callScope->resolveName($methodCallNode->class));

$calledOnType = $callScope->resolveTypeByName($methodCallNode->class);
}
if ($classType->isSuperTypeOf($calledOnType)->no()) {
continue;
Expand Down
4 changes: 1 addition & 3 deletions src/Rules/Classes/ImpossibleInstanceOfRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeCombinator;
Expand Down Expand Up @@ -42,8 +41,7 @@ public function processNode(Node $node, Scope $scope): array
$expressionType = $scope->getType($node->expr);

if ($node->class instanceof Node\Name) {
$className = $scope->resolveName($node->class);
$classType = new ObjectType($className);
$classType = $scope->resolveTypeByName($node->class);
} else {
$classType = $scope->getType($node->class);
$allowed = TypeCombinator::union(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectType;

/**
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\StaticCall>
Expand Down Expand Up @@ -102,7 +101,7 @@ private function getMethod(
): MethodReflection
{
if ($class instanceof Node\Name) {
$calledOnType = new ObjectType($scope->resolveName($class));
$calledOnType = $scope->resolveTypeByName($class);
} else {
$calledOnType = $scope->getType($class);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/DeadCode/UnusedPrivateMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function processNode(Node $node, Scope $scope): array
if (!$methodCallNode->class instanceof Node\Name) {
continue;
}
$calledOnType = new ObjectType($callScope->resolveName($methodCallNode->class));
$calledOnType = $scope->resolveTypeByName($methodCallNode->class);
}
if ($classType->isSuperTypeOf($calledOnType)->no()) {
continue;
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/DeadCode/UnusedPrivatePropertyRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public function processNode(Node $node, Scope $scope): array
continue;
}

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

if ($classType->isSuperTypeOf($fetchedOnType)->no()) {
Expand Down
5 changes: 2 additions & 3 deletions src/Rules/Properties/PropertyReflectionFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PhpParser\Node\VarLikeIdentifier;
use PHPStan\Analyser\Scope;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -57,7 +56,7 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a
}

if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
$propertyHolderType = $scope->resolveTypeByName($propertyFetch->class);
} else {
$propertyHolderType = $scope->getType($propertyFetch->class);
}
Expand Down Expand Up @@ -114,7 +113,7 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F
}

if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
$propertyHolderType = $scope->resolveTypeByName($propertyFetch->class);
} else {
$propertyHolderType = $scope->getType($propertyFetch->class);
}
Expand Down
9 changes: 2 additions & 7 deletions src/Type/Php/IsAFunctionTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StaticType;

class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
Expand Down Expand Up @@ -47,12 +46,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
&& $classNameArgExpr->name instanceof \PhpParser\Node\Identifier
&& strtolower($classNameArgExpr->name->name) === 'class'
) {
$className = $scope->resolveName($classNameArgExpr->class);
if (strtolower($classNameArgExpr->class->toString()) === 'static') {
$objectType = new StaticType($className);
} else {
$objectType = new ObjectType($className);
}
$objectType = $scope->resolveTypeByName($classNameArgExpr->class);
// todo static => StaticType
$types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context);
} elseif ($classNameArgExprType instanceof ConstantStringType) {
$objectType = new ObjectType($classNameArgExprType->getValue());
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5651,6 +5651,11 @@ public function dataGenericUnions(): array
return $this->gatherAssertTypes(__DIR__ . '/data/generic-unions.php');
}

public function dataGenericParent(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/generic-parent.php');
}

/**
* @dataProvider dataArrayFunctions
* @param string $description
Expand Down Expand Up @@ -11264,6 +11269,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataGenericTraits
* @dataProvider dataBug4423
* @dataProvider dataGenericUnions
* @dataProvider dataGenericParent
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
41 changes: 41 additions & 0 deletions tests/PHPStan/Analyser/data/generic-parent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace GenericParent;

use function PHPStan\Analyser\assertType;

interface Animal
{

}

interface Dog extends Animal
{

}

/**
* @template T of Animal
*/
class Foo
{

/** @return T */
public function getAnimal(): Animal
{

}

}

/** @extends Foo<Dog> */
class Bar extends Foo
{

public function doFoo()
{
assertType(Dog::class, parent::getAnimal());
assertType(Dog::class, Foo::getAnimal());
}

}

0 comments on commit 4acbc6c

Please sign in to comment.