diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 9abde2a87b..837bbd5aa3 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -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 { @@ -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') { @@ -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) { @@ -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) { @@ -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 */ diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a87651744d..6609f9756e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -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); } @@ -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); diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 0844263e31..56e532b545 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -70,6 +70,8 @@ public function doNotTreatPhpDocTypesAsCertain(): self; public function resolveName(Name $name): string; + public function resolveTypeByName(Name $name): Type; + /** * @param mixed $value */ diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9d282f3b9c..9c7c74023e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -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); } diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 15baab2655..280555c926 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -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; diff --git a/src/Rules/Classes/ImpossibleInstanceOfRule.php b/src/Rules/Classes/ImpossibleInstanceOfRule.php index 02e3e4c63f..efe3a41165 100644 --- a/src/Rules/Classes/ImpossibleInstanceOfRule.php +++ b/src/Rules/Classes/ImpossibleInstanceOfRule.php @@ -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; @@ -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( diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index e49eb8d430..5c66ad5413 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -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> @@ -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); } diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index 9c68f9e775..4d3c3cb2fb 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -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; diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 9059672b18..6ba7338b28 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -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()) { diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index eefd26f61e..93bc4a0f74 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -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; @@ -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); } @@ -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); } diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index 2d7db134e3..755d7a4020 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -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 { @@ -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()); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7abef28c57..9d3e1fdc48 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -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 @@ -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 diff --git a/tests/PHPStan/Analyser/data/generic-parent.php b/tests/PHPStan/Analyser/data/generic-parent.php new file mode 100644 index 0000000000..93ca0ad274 --- /dev/null +++ b/tests/PHPStan/Analyser/data/generic-parent.php @@ -0,0 +1,41 @@ + */ +class Bar extends Foo +{ + + public function doFoo() + { + assertType(Dog::class, parent::getAnimal()); + assertType(Dog::class, Foo::getAnimal()); + } + +}