diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 3e76a98ef8..872ecc05ba 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3322,7 +3322,7 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type } } - return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection()->getName() : null); + return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection() : null); } public function enterForeach(Expr $iteratee, string $valueName, ?string $keyName): self diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 07e55ec642..9f5bc22434 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -123,7 +123,7 @@ public function processNode(Node $node, Scope $scope): array $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()) ))->nonIgnorable()->build(); } else { - $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()->getName()); + $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()); if (!$prototype->getNativeType()->equals($nativeType)) { $typeErrors[] = RuleErrorBuilder::message(sprintf( 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', @@ -141,7 +141,7 @@ public function processNode(Node $node, Scope $scope): array 'Property %s::$%s (%s) overriding property %s::$%s should not have a native type.', $classReflection->getDisplayName(), $node->getName(), - ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()->getName())->describe(VerbosityLevel::typeOnly()), + ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection())->describe(VerbosityLevel::typeOnly()), $prototype->getDeclaringClass()->getDisplayName(), $node->getName() ))->nonIgnorable()->build(); diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index a6910b65ae..00d3baf951 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -4,6 +4,7 @@ use PhpParser\Node\Name; use PhpParser\Node\NullableType; +use PHPStan\Reflection\ClassReflection; use PHPStan\Type\Constant\ConstantBooleanType; class ParserNodeTypeToPHPStanType @@ -11,22 +12,24 @@ class ParserNodeTypeToPHPStanType /** * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type - * @param string|null $className + * @param ClassReflection|null $classReflection * @return Type */ - public static function resolve($type, ?string $className): Type + public static function resolve($type, ?ClassReflection $classReflection): Type { if ($type === null) { return new MixedType(); } elseif ($type instanceof Name) { $typeClassName = (string) $type; $lowercasedClassName = strtolower($typeClassName); - if ($className !== null && in_array($lowercasedClassName, ['self', 'static'], true)) { - $typeClassName = $className; + if ($classReflection !== null && in_array($lowercasedClassName, ['self', 'static'], true)) { + $typeClassName = $classReflection->getName(); } elseif ( $lowercasedClassName === 'parent' + && $classReflection !== null + && $classReflection->getParentClass() !== false ) { - throw new \PHPStan\ShouldNotHappenException('parent type is not supported here'); + $typeClassName = $classReflection->getParentClass()->getName(); } if ($lowercasedClassName === 'static') { @@ -35,11 +38,11 @@ public static function resolve($type, ?string $className): Type return new ObjectType($typeClassName); } elseif ($type instanceof NullableType) { - return TypeCombinator::addNull(self::resolve($type->type, $className)); + return TypeCombinator::addNull(self::resolve($type->type, $classReflection)); } elseif ($type instanceof \PhpParser\Node\UnionType) { $types = []; foreach ($type->types as $unionTypeType) { - $types[] = self::resolve($unionTypeType, $className); + $types[] = self::resolve($unionTypeType, $classReflection); } return TypeCombinator::union(...$types); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 4cff1f74e5..74f051acd9 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -426,6 +426,12 @@ public function testBug5231Two(): void $this->assertNotEmpty($errors); } + public function testBug5529(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5529.php'); + $this->assertCount(0, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 16641b436f..7f7ced314a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -477,6 +477,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var-returns-non-empty-string.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5529.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5529.php b/tests/PHPStan/Analyser/data/bug-5529.php new file mode 100644 index 0000000000..1e787f602e --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5529.php @@ -0,0 +1,24 @@ +run()); +};