From 429255cde887e5ac752da3bbf25210326c4f89d9 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 28 Jun 2026 16:52:25 +0200 Subject: [PATCH 1/3] [DeadCode] Keep private method called via self::class static call A private method invoked through a class-string static call, e.g. self::class::sampleClass(), is not detected by the usage analyzer and would be wrongly removed. Keep such methods. --- .../keep_self_class_static_call.php.inc | 18 +++++++++ .../RemoveUnusedPrivateMethodRector.php | 40 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc new file mode 100644 index 00000000000..660a8124339 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector/Fixture/keep_self_class_static_call.php.inc @@ -0,0 +1,18 @@ +resolveDataProviderMethodNames($node); + // methods invoked via a class-string static call, e.g. self::class::sampleClass(), + // are not seen by the usage analyzer + $classStringCallMethodNames = $this->resolveClassStringStaticCallNames($node); + foreach ($node->stmts as $classStmtKey => $classStmt) { if (! $classStmt instanceof ClassMethod) { continue; @@ -118,6 +125,10 @@ public function refactor(Node $node): ?Node continue; } + if ($this->isNames($classMethod, $classStringCallMethodNames)) { + continue; + } + unset($node->stmts[$classStmtKey]); $hasChanged = true; } @@ -153,6 +164,35 @@ private function shouldSkip(ClassMethod $classMethod, ClassReflection $classRefl return $classReflection->hasMethod(MethodName::CALL); } + /** + * @return string[] + */ + private function resolveClassStringStaticCallNames(Class_ $class): array + { + $methodNames = []; + + /** @var StaticCall[] $staticCalls */ + $staticCalls = $this->betterNodeFinder->findInstanceOf($class->stmts, StaticCall::class); + foreach ($staticCalls as $staticCall) { + // e.g. self::class::sampleClass() - the called class is a ::class expression + if (! $staticCall->class instanceof ClassConstFetch) { + continue; + } + + if (! $this->isName($staticCall->class->name, 'class')) { + continue; + } + + if (! $staticCall->name instanceof Identifier) { + continue; + } + + $methodNames[] = $staticCall->name->toString(); + } + + return $methodNames; + } + private function hasDynamicMethodCallOnFetchThis(ClassMethod $classMethod): bool { return (bool) $this->betterNodeFinder->findFirst( From 93228601aec20c291335441bae008de5477b089b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 30 Jun 2026 17:09:05 +0200 Subject: [PATCH 2/3] [DeadCode] Add @see phpstan reference for class-string static call detection --- .../Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php index a1243e362ca..f6b9f364217 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php @@ -24,6 +24,9 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** + * Class-string static call detection mirrors PHPStan's UnusedPrivateMethodRule fix + * @see https://github.com/phpstan/phpstan-src/pull/5953 + * * @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodRector\RemoveUnusedPrivateMethodRectorTest */ final class RemoveUnusedPrivateMethodRector extends AbstractRector From a3c9609b116ff6aaf781d07b805aad28b8b3f200 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 30 Jun 2026 17:11:03 +0200 Subject: [PATCH 3/3] [DeadCode] Move phpstan @see to resolveClassStringStaticCallNames method --- .../Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php index f6b9f364217..784b9730507 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveUnusedPrivateMethodRector.php @@ -24,9 +24,6 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * Class-string static call detection mirrors PHPStan's UnusedPrivateMethodRule fix - * @see https://github.com/phpstan/phpstan-src/pull/5953 - * * @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodRector\RemoveUnusedPrivateMethodRectorTest */ final class RemoveUnusedPrivateMethodRector extends AbstractRector @@ -168,6 +165,9 @@ private function shouldSkip(ClassMethod $classMethod, ClassReflection $classRefl } /** + * Mirrors PHPStan's UnusedPrivateMethodRule fix for class-string static calls + * @see https://github.com/phpstan/phpstan-src/pull/5953 + * * @return string[] */ private function resolveClassStringStaticCallNames(Class_ $class): array