diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 4b2c484ef9..d75ad079a2 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -171,7 +171,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $genericObjectTypeCheck, $unresolvableTypeHelper, ), - new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper), + new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper, $fileTypeMapper), new InvalidPhpDocTagValueRule( $container->getByType(Lexer::class), $container->getByType(PhpDocParser::class), diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index bc053796de..21bc4b7da0 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -10,9 +10,12 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateType; +use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\VerbosityLevel; use function array_merge; +use function count; use function sprintf; /** @@ -24,6 +27,7 @@ class IncompatiblePropertyPhpDocTypeRule implements Rule public function __construct( private GenericObjectTypeCheck $genericObjectTypeCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, + private FileTypeMapper $fileTypeMapper, ) { } @@ -40,15 +44,33 @@ public function processNode(Node $node, Scope $scope): array } $propertyName = $node->getName(); - $propertyReflection = $scope->getClassReflection()->getNativeProperty($propertyName); + $phpDoc = $node->getPhpDoc(); + if ($phpDoc === null) { + return []; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + null, + $phpDoc, + ); + + $varTags = $resolvedPhpDoc->getVarTags(); + $phpDocType = null; + if (isset($varTags[0]) && count($varTags) === 1) { + $phpDocType = $varTags[0]->getType(); + } elseif (isset($varTags[$propertyName])) { + $phpDocType = $varTags[$propertyName]->getType(); + } - if (!$propertyReflection->hasPhpDocType()) { + if ($phpDocType === null) { return []; } - $phpDocType = $propertyReflection->getPhpDocType(); $description = 'PHPDoc tag @var'; - if ($propertyReflection->isPromoted()) { + if ($node->isPromoted()) { $description = 'PHPDoc type'; } @@ -59,18 +81,18 @@ public function processNode(Node $node, Scope $scope): array $messages[] = RuleErrorBuilder::message(sprintf( '%s for property %s::$%s contains unresolvable type.', $description, - $propertyReflection->getDeclaringClass()->getName(), + $scope->getClassReflection()->getDisplayName(), $propertyName, ))->build(); } - $nativeType = $propertyReflection->getNativeType(); + $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()); $isSuperType = $nativeType->isSuperTypeOf($phpDocType); if ($isSuperType->no()) { $messages[] = RuleErrorBuilder::message(sprintf( '%s for property %s::$%s with type %s is incompatible with native type %s.', $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), + $scope->getClassReflection()->getDisplayName(), $propertyName, $phpDocType->describe(VerbosityLevel::typeOnly()), $nativeType->describe(VerbosityLevel::typeOnly()), @@ -80,7 +102,7 @@ public function processNode(Node $node, Scope $scope): array $errorBuilder = RuleErrorBuilder::message(sprintf( '%s for property %s::$%s with type %s is not subtype of native type %s.', $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), + $scope->getClassReflection()->getDisplayName(), $propertyName, $phpDocType->describe(VerbosityLevel::typeOnly()), $nativeType->describe(VerbosityLevel::typeOnly()), @@ -93,7 +115,7 @@ public function processNode(Node $node, Scope $scope): array $messages[] = $errorBuilder->build(); } - $className = SprintfHelper::escapeFormatString($propertyReflection->getDeclaringClass()->getDisplayName()); + $className = SprintfHelper::escapeFormatString($scope->getClassReflection()->getDisplayName()); $escapedPropertyName = SprintfHelper::escapeFormatString($propertyName); $messages = array_merge($messages, $this->genericObjectTypeCheck->check( diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php index 4237a2667a..697d87b6ca 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\FileTypeMapper; /** * @extends RuleTestCase @@ -14,7 +15,11 @@ class IncompatiblePropertyPhpDocTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IncompatiblePropertyPhpDocTypeRule(new GenericObjectTypeCheck(), new UnresolvableTypeHelper()); + return new IncompatiblePropertyPhpDocTypeRule( + new GenericObjectTypeCheck(), + new UnresolvableTypeHelper(), + self::getContainer()->getByType(FileTypeMapper::class), + ); } public function testRule(): void