diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 02fed04bcc..91aaf7037c 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -76,9 +76,9 @@ public function resolveVarTags(PhpDocNode $phpDocNode, NameScope $nameScope): ar } if ($tagValue->variableName !== '') { $variableName = substr($tagValue->variableName, 1); - $resolved[$variableName] = new VarTag($type); + $resolved[$variableName] = new VarTag($type, true); } else { - $varTag = new VarTag($type); + $varTag = new VarTag($type, true); $tagResolved[] = $varTag; } } diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index ed45d60e48..771490e7c9 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -887,7 +887,7 @@ private static function mergeVarTags(array $varTags, array $parents, array $pare private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array { foreach ($parent->getVarTags() as $key => $parentVarTag) { - return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock, TemplateTypeVariance::createInvariant())]; + return [$key => self::resolveTemplateTypeInTag($parentVarTag->toImplicit(), $phpDocBlock, TemplateTypeVariance::createInvariant())]; } return null; diff --git a/src/PhpDoc/Tag/VarTag.php b/src/PhpDoc/Tag/VarTag.php index 0d93daeac8..b1ae823ea0 100644 --- a/src/PhpDoc/Tag/VarTag.php +++ b/src/PhpDoc/Tag/VarTag.php @@ -11,7 +11,7 @@ class VarTag implements TypedTag { - public function __construct(private Type $type) + public function __construct(private Type $type, private bool $isExplicit) { } @@ -25,7 +25,17 @@ public function getType(): Type */ public function withType(Type $type): TypedTag { - return new self($type); + return new self($type, $this->isExplicit); + } + + public function isExplicit(): bool + { + return $this->isExplicit; + } + + public function toImplicit(): self + { + return new self($this->type, false); } } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index d031cb8816..23f1432c3c 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1094,10 +1094,6 @@ public function getConstant(string $name): ClassConstantReflection $isDeprecated = $resolvedPhpDoc->isDeprecated(); $isInternal = $resolvedPhpDoc->isInternal(); $isFinal = $resolvedPhpDoc->isFinal(); - $varTags = $resolvedPhpDoc->getVarTags(); - if (isset($varTags[0]) && count($varTags) === 1) { - $phpDocType = $varTags[0]->getType(); - } $nativeType = null; if ($reflectionConstant->getType() !== null) { @@ -1106,6 +1102,14 @@ public function getConstant(string $name): ClassConstantReflection $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType']; } + $varTags = $resolvedPhpDoc->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $varTag = $varTags[0]; + if ($varTag->isExplicit() || $nativeType === null || $nativeType->isSuperTypeOf($varTag->getType())->yes()) { + $phpDocType = $varTag->getType(); + } + } + $this->constants[$name] = new ClassConstantReflection( $this->initializerExprTypeResolver, $declaringClass, diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php index a66be60a2d..91a74e428b 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php @@ -50,4 +50,13 @@ public function testNativeType(): void ]); } + public function testBug10911(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->analyse([__DIR__ . '/data/bug-10911.php'], []); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-10911.php b/tests/PHPStan/Rules/PhpDoc/data/bug-10911.php new file mode 100644 index 0000000000..7cce5e8b88 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-10911.php @@ -0,0 +1,27 @@ += 8.3 + +namespace Bug10911; + +abstract class Model +{ + /** + * The name of the "created at" column. + * + * @var string|null + */ + const CREATED_AT = 'created_at'; + + /** + * The name of the "updated at" column. + * + * @var string|null + */ + const UPDATED_AT = 'updated_at'; +} + +class TestModel extends Model +{ + const string CREATED_AT = 'data_criacao'; + const string UPDATED_AT = 'data_alteracao'; + const DELETED_AT = null; +}