Inherit PHPDoc type from parent/interface properties for promoted constructor properties#5581
Inherit PHPDoc type from parent/interface properties for promoted constructor properties#5581phpstan-bot wants to merge 3 commits intophpstan:2.1.xfrom
Conversation
…structor properties - In `PhpClassReflectionExtension::createProperty()`, promoted properties skipped `PhpDocInheritanceResolver::resolvePhpDocForProperty()` entirely, so their PHPDoc type was never inherited from parent/interface properties. - Add a fallback that calls `resolvePhpDocForProperty()` when a promoted property has no local `@var` tag and no constructor `@param` tag, inheriting the PHPDoc type from the overridden property. - Priority is preserved: local `@var` > constructor `@param` > inherited type. - Tested with interfaces (property hooks), parent classes, abstract classes, multi-level inheritance, and generic interfaces with template types.
VincentLanglet
left a comment
There was a problem hiding this comment.
Merge the nsrt and RuleTest fixture file into a single one
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Merged the two test fixture files into a single |
VincentLanglet
left a comment
There was a problem hiding this comment.
We don't benefit of all the resolution
$isInternal = $resolvedPhpDoc->isInternal();
$isReadOnlyByPhpDoc = $isReadOnlyByPhpDoc || $resolvedPhpDoc->isReadOnly();
$isFinal = $isFinal || $resolvedPhpDoc->isFinal();
$isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation();
We might just do $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForProperty before the if ($resolvedPhpDoc !== null) { condition.
Also, we'll need a test with a class Bar extending Foo where Foo has a private property $foo and Bar has a public property $foo to ensure the definition does not conflict now we resolve by inheritance
…PhpDocForProperty Move the resolvePhpDocForProperty call before the metadata extraction block so promoted properties also inherit isInternal, isReadOnlyByPhpDoc, isFinal, and isAllowedPrivateMutation from parent/interface properties. The @param tag still overrides inherited types but not explicit local @var annotations. Add test for private parent property not conflicting with child's promoted property. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Here's what I changed to address VincentLanglet's review:
|
Summary
When an interface or parent class declares a property with a PHPDoc type (e.g.
/** @var array<string> */), regular (non-promoted) properties in implementing/extending classes correctly inherit that PHPDoc type. However, promoted constructor properties did not inherit the type, causing false positive covariance errors like "PHPDoc type array of property C::$test is not covariant with PHPDoc type array of overridden property A::$test".This PR makes promoted properties inherit PHPDoc types from parent/interface properties when they don't have their own explicit type annotation.
Changes
src/Reflection/Php/PhpClassReflectionExtension.php: Added a fallback increateProperty()that callsPhpDocInheritanceResolver::resolvePhpDocForProperty()for promoted properties when no type is found from local@varor constructor@paramtags. This mirrors what already happens for regular properties.Root cause
In
PhpClassReflectionExtension::createProperty(), the PHPDoc resolution logic had two branches:$constructorName === null): CalledresolvePhpDocForProperty()which merges the current property's PHPDoc with parent/interface PHPDoc$constructorName !== null): Only processed local@vartags and constructor@paramtags, never callingresolvePhpDocForProperty()The fix adds a third fallback after the existing
@varand@paramchecks: if$phpDocTypeis still null for a promoted property, callresolvePhpDocForProperty()to inherit from the parent/interface.The priority order is preserved correctly:
@varon the promoted property (most explicit)@paramfor the parameterAnalogous cases probed
All of these were tested and confirmed working:
Container<string>)@paramcorrectly overrides inherited type@varon promoted property correctly overrides inherited typeTest
tests/PHPStan/Analyser/nsrt/bug-14564.php): Verifies inferred types for promoted properties across all analogous cases — interface, parent class, multi-level inheritance, generics,@paramoverride, and@varoverride.tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php::testBug14564): Verifies no false positive covariance errors fromOverridingPropertyRulefor promoted properties inheriting PHPDoc types from interfaces and parent classes.Fixes phpstan/phpstan#14564