Skip to content

[DeadCode] Remove unreachable class-like checks in Class_ only rules - #8217

Merged
TomasVotruba merged 1 commit into
mainfrom
remove-dead-classlike-checks
Jul 29, 2026
Merged

[DeadCode] Remove unreachable class-like checks in Class_ only rules#8217
TomasVotruba merged 1 commit into
mainfrom
remove-dead-classlike-checks

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

Draft. Small first step of the "remove PHPStan, don't wrap it" thread — the subset where reflection is answering a question that cannot have another answer.

Why these are constant

Trait_, Interface_ and Enum_ are sibling php-parser node classes, not subclasses of Class_:

is_subclass_of(PhpParser\Node\Stmt\Enum_::class,      PhpParser\Node\Stmt\Class_::class); // false
is_subclass_of(PhpParser\Node\Stmt\Trait_::class,     PhpParser\Node\Stmt\Class_::class); // false
is_subclass_of(PhpParser\Node\Stmt\Interface_::class, PhpParser\Node\Stmt\Class_::class); // false

All three rules below declare:

public function getNodeTypes(): array
{
    return [Class_::class];
}

so the reflection they resolve from their own node is always a class. isTrait() and isInterface() are always false, isClass() always true.

Changes

RemoveUnusedPrivateMethodRector — never reached, node is always Class_:

-        // unreliable to detect trait, interface, anonymous class: doesn't make sense
-        if ($classReflection->isTrait()) {
-            return true;
-        }
-
-        if ($classReflection->isInterface()) {
-            return true;
-        }
-
-        if ($classReflection->isAnonymous()) {
+        // unreliable to detect anonymous class: doesn't make sense
+        if ($classReflection->isAnonymous()) {
             return true;
         }

ParamTypeByParentCallTypeRector! $classReflection->isClass() is always false, so the guard reduces to the reflection-resolved check that precedes it:

         $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod);
-        if (! $classReflection instanceof ClassReflection) {
-            return true;
-        }
-
-        return ! $classReflection->isClass();
+        return ! $classReflection instanceof ClassReflection;

RemoveArgumentFromDefaultParentCallRector$classReflection is the outer node's own reflection, so isClass() is loop-invariant and always true:

         $ancestors = array_filter(
             $classReflection->getAncestors(),
-            fn (ClassReflection $ancestorClassReflection): bool => $classReflection->isClass() && $ancestorClassReflection->getName() !== $classReflection->getName()
+            fn (ClassReflection $ancestorClassReflection): bool => $ancestorClassReflection->getName() !== $classReflection->getName()
         );

Scope

No behaviour change — these are provably constant conditions, not heuristics. 15 lines removed, 3 added.

Both isClass() sites were checked to be the node's own reflection (resolveClassReflection($node) / ($classMethod)), not an ancestor's. Ancestor-reflection checks like $parentClassReflection->isTrait() in AddOverrideAttributeToOverriddenMethodsRector are genuinely needed and left alone.

Touches the same shouldSkip() as #TBD (native node API); whichever lands second needs a trivial rebase.

Trait_, Interface_ and Enum_ are separate php-parser node classes, not
subclasses of Class_. A rule whose getNodeTypes() returns [Class_::class]
can therefore never see one, so isTrait(), isInterface() and isClass()
checks on the node own class reflection are constant.
@TomasVotruba
TomasVotruba marked this pull request as ready for review July 29, 2026 16:38
@TomasVotruba
TomasVotruba merged commit d0f304c into main Jul 29, 2026
65 checks passed
@TomasVotruba
TomasVotruba deleted the remove-dead-classlike-checks branch July 29, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant