diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index 52ec29d01a..e5b561af65 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -84,7 +84,16 @@ public function processNode(Node $node, Scope $scope): array $methodNameType = $callScope->getType($methodCallNode->name); $strings = $methodNameType->getConstantStrings(); if (count($strings) === 0) { - return []; + // handle subtractions of a dynamic method call + foreach ($methods as $lowerMethodName => $method) { + if ((new ConstantStringType($method->getNode()->name->toString()))->isSuperTypeOf($methodNameType)->no()) { + continue; + } + + unset($methods[$lowerMethodName]); + } + + continue; } $methodNames = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $strings); diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index f3373d4c10..b41185b5c9 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -120,7 +120,16 @@ public function processNode(Node $node, Scope $scope): array $propertyNameType = $usage->getScope()->getType($fetch->name); $strings = $propertyNameType->getConstantStrings(); if (count($strings) === 0) { - return []; + // handle subtractions of a dynamic property fetch + foreach ($properties as $propertyName => $data) { + if ((new ConstantStringType($propertyName))->isSuperTypeOf($propertyNameType)->no()) { + continue; + } + + unset($properties[$propertyName]); + } + + continue; } $propertyNames = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $strings); diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index 246237f97f..ca57318a59 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -133,4 +133,14 @@ public function testBug9765(): void $this->analyse([__DIR__ . '/data/bug-9765.php'], []); } + public function testBug11802(): void + { + $this->analyse([__DIR__ . '/data/bug-11802b.php'], [ + [ + 'Method Bug11802b\HelloWorld::doBar() is unused.', + 10, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 9e97e8bc4a..b4d2f4de0b 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -336,4 +336,19 @@ public function testBug7251(): void $this->analyse([__DIR__ . '/data/bug-7251.php'], []); } + public function testBug11802(): void + { + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-11802.php'], [ + [ + 'Property Bug11802\HelloWorld::$isFinal is never read, only written.', + 8, + $tip, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11802.php b/tests/PHPStan/Rules/DeadCode/data/bug-11802.php new file mode 100644 index 0000000000..a97152f385 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11802.php @@ -0,0 +1,20 @@ += 8.0 + +namespace Bug11802; + +class HelloWorld +{ + public function __construct( + private bool $isFinal, + private bool $used + ) + { + } + + public function doFoo(HelloWorld $x, $y): void + { + if ($y !== 'isFinal') { + $s = $x->{$y}; + } + } +} diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11802b.php b/tests/PHPStan/Rules/DeadCode/data/bug-11802b.php new file mode 100644 index 0000000000..68bc97f2ac --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11802b.php @@ -0,0 +1,19 @@ += 8.0 + +namespace Bug11802b; + +class HelloWorld +{ + public function __construct( + ) {} + + private function doBar():void {} + + private function doFooBar():void {} + + public function doFoo(HelloWorld $x, $y): void { + if ($y !== 'doBar') { + $s = $x->$y(); + } + } +}