diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index e9b7b2b51a..754be7bc6e 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -138,6 +138,10 @@ public function getUninitializedProperties( continue; } + if ($propertyReflection->getDeclaringClass()->getName() !== $classReflection->getName()) { + $is = TrinaryLogic::createYes(); + } + foreach ($extensions as $extension) { if (!$extension->isInitialized($propertyReflection, $property->getName())) { continue; diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php index 9668ddbe6d..868e3d77e0 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -154,4 +154,15 @@ public function testBug13856(): void $this->analyse([__DIR__ . '/data/bug-13856-phpdoc.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testBug14983(): void + { + $this->analyse([__DIR__ . '/data/bug-14983-phpdoc.php'], [ + [ + 'Class Bug14983PhpDoc\UninitializedFromTrait has an uninitialized @readonly property $property. Assign it in the constructor.', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php index 2a023b0e7c..1f6332b72d 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php @@ -152,6 +152,17 @@ public function testRule(): void ]); } + #[RequiresPhp('>= 8.1.0')] + public function testBug14983(): void + { + $this->analyse([__DIR__ . '/data/bug-14983.php'], [ + [ + 'Class Bug14983\UninitializedFromTrait has an uninitialized readonly property $property. Assign it in the constructor.', + 8, + ], + ]); + } + #[RequiresPhp('>= 8.1.0')] public function testBug7119(): void { diff --git a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php index 0d9827885e..d1b137e35c 100644 --- a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php @@ -233,4 +233,10 @@ public function testBug12547(): void $this->analyse([__DIR__ . '/data/bug-12547.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testBug14983(): void + { + $this->analyse([__DIR__ . '/data/bug-14983-uninitialized.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-14983-phpdoc.php b/tests/PHPStan/Rules/Properties/data/bug-14983-phpdoc.php new file mode 100644 index 0000000000..0596c5e808 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14983-phpdoc.php @@ -0,0 +1,43 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug14983PhpDoc; + +trait TraitA { + /** @readonly */ + protected string $property; +} + +trait TraitB { + use TraitA; +} + +class ClassA { + use TraitA; + + public function __construct( + /** @readonly */ + protected string $property, + ) {} +} + +class ClassB extends ClassA { + use TraitB; +} + +class BodyInitClassA { + use TraitA; + + public function __construct(string $property) { + $this->property = $property; + } +} + +class BodyInitClassB extends BodyInitClassA { + use TraitB; +} + +class UninitializedFromTrait { + use TraitA; +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-14983-uninitialized.php b/tests/PHPStan/Rules/Properties/data/bug-14983-uninitialized.php new file mode 100644 index 0000000000..cc0cbc3400 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14983-uninitialized.php @@ -0,0 +1,35 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug14983Uninitialized; + +trait TraitA { + protected string $property; +} + +trait TraitB { + use TraitA; +} + +class ClassA { + use TraitA; + + public function __construct(protected string $property) {} +} + +class ClassB extends ClassA { + use TraitB; +} + +class BodyInitClassA { + use TraitA; + + public function __construct(string $property) { + $this->property = $property; + } +} + +class BodyInitClassB extends BodyInitClassA { + use TraitB; +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-14983.php b/tests/PHPStan/Rules/Properties/data/bug-14983.php new file mode 100644 index 0000000000..6cdeb0b52d --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14983.php @@ -0,0 +1,55 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14983; + +trait TraitA { + protected readonly string $property; +} + +trait TraitB { + use TraitA; +} + +class ClassA { + use TraitA; + + public function __construct(protected readonly string $property) {} +} + +class ClassB extends ClassA { + use TraitB; +} + +class BodyInitClassA { + use TraitA; + + public function __construct(string $property) { + $this->property = $property; + } +} + +class BodyInitClassB extends BodyInitClassA { + use TraitB; +} + +trait GrandTrait { + use TraitA; +} + +class GrandParent1 { + use TraitA; + + public function __construct(protected readonly string $property) {} +} + +class Parent1 extends GrandParent1 {} + +class Child1 extends Parent1 { + use GrandTrait; +} + +class UninitializedFromTrait { + use GrandTrait; +}