From da12dc2d10aff3d1dcd79c8aa4cbdc24a5e5ed26 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 15:38:54 +0100 Subject: [PATCH] OverridingPropertyRule - check for final --- src/Reflection/Php/PhpPropertyReflection.php | 9 +++++- .../Properties/OverridingPropertyRule.php | 12 ++++++++ .../Properties/OverridingPropertyRuleTest.php | 28 ++++++++++++++++++ .../data/overriding-final-property.php | 29 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/overriding-final-property.php diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 1fa95c67aa..3ff948eb3a 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -234,7 +234,14 @@ public function isAbstract(): TrinaryLogic public function isFinal(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); + if ($this->reflection->isFinal()) { + return TrinaryLogic::createYes(); + } + if ($this->reflection->isPrivate()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createFromBoolean($this->isPrivateSet()); } public function isVirtual(): TrinaryLogic diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 5767635e27..61325ed508 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -102,6 +102,18 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.visibility')->nonIgnorable()->build(); } + if ($prototype->isFinal()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s overrides final property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.parentPropertyFinal') + ->nonIgnorable() + ->build(); + } + $typeErrors = []; $nativeType = $node->getNativeType(); if ($prototype->hasNativeType()) { diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index dafac4e5f0..954eaae511 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function sprintf; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -171,4 +172,31 @@ public function testBug7692(): void $this->analyse([__DIR__ . '/data/bug-7692.php'], []); } + public function testFinal(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->reportMaybes = true; + $this->analyse([__DIR__ . '/data/overriding-final-property.php'], [ + [ + 'Property OverridingFinalProperty\Bar::$a overrides final property OverridingFinalProperty\Foo::$a.', + 21, + ], + [ + 'Property OverridingFinalProperty\Bar::$b overrides final property OverridingFinalProperty\Foo::$b.', + 23, + ], + [ + 'Property OverridingFinalProperty\Bar::$c overrides final property OverridingFinalProperty\Foo::$c.', + 25, + ], + [ + 'Property OverridingFinalProperty\Bar::$d overrides final property OverridingFinalProperty\Foo::$d.', + 27, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php new file mode 100644 index 0000000000..662f285a7e --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php @@ -0,0 +1,29 @@ += 8.4 + +namespace OverridingFinalProperty; + +class Foo +{ + + final public $a; + + final protected $b; + + public private(set) $c; + + protected private(set) $d; + +} + +class Bar extends Foo +{ + + public $a; + + public $b; + + public $c; + + public $d; + +}