Treat all in-scope variables as used when an unused-parameter body accesses a variable variable ($$name)#6058
Conversation
…cesses a variable variable (`$$name`) - `UnusedFunctionParametersCheck::getUsedVariables()` now returns `$scope->getDefinedVariables()` when it encounters a `Variable` node whose name is not a static string (a variable variable like `$$name`), mirroring the existing handling for `include`/`require`/`eval` and `func_get_args()`/`get_defined_vars()`. - This covers both callers of the shared check: `UnusedClosureUsesRule` (`closure.unusedUse`) and `UnusedConstructorParametersRule` (`constructor.unusedParameter`), since the accessed variable cannot be statically determined. - Merged the two `Variable` branches to avoid a redundant `is_string()` check flagged by self-analysis. - Added regression tests: a closure `use ($container, $name)` body containing `$$name`, and a constructor whose body reads `$$name`; also added the issue's verbatim closure-with-`require` reproducer to `unused-closure-uses.php`.
|
Should still be reported as unused ? |
staabm
left a comment
There was a problem hiding this comment.
check the comment #6058 (comment)
…very variable used When an unused-parameter body accesses `$$name` where `$name` resolves to known constant string(s), the accessed variable can be determined statically, so mark exactly those as used instead of conservatively treating every in-scope variable as used. The fall-back to `getDefinedVariables()` now only applies when the name cannot be resolved to constant strings. This keeps reporting a genuinely unused `use`/parameter when `$$name` provably refers to a different variable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
I've addressed the review. Here's what the referenced comment (VincentLanglet's) was asking:
Yes — and my original PR was wrong there. The first version conservatively marked every in-scope variable as used whenever the body contained any variable variable, which is a false negative when What I changed
This mirrors the existing precedent in Tests — added VincentLanglet's exact scenario to Verification
Committed and pushed as one logical change. |
Summary
The reported bug — a false
closure.unusedUseerror for ausevariable that is only accessed inside aninclude/require/eval— was already fixed on2.2.x(commit 501ac99) by treating all in-scope variables as used when the body containsinclude/require/eval. Per the maintainer, only a regression test was outstanding.While adding that test, I probed the parallel "dynamic variable access PHPStan cannot resolve statically" axis and found the same false positive for variable variables (
$$name): PHPStan cannot know which variable$$nameresolves to, yet it still reported the captureduse/constructor parameter as unused. This PR fixes that analogous case and adds regression tests for both the original and the new case.Changes
src/Rules/UnusedFunctionParametersCheck.php: ingetUsedVariables(), when aVariablenode has a non-string name (a variable variable such as$$name), return$scope->getDefinedVariables()so every in-scope variable is considered used — the same conservative treatment already applied toinclude/require/evalandfunc_get_args()/get_defined_vars(). The twoVariablebranches were merged to keep the narrowing clean and avoid a now-redundantis_string()check.tests/PHPStan/Rules/Functions/data/unused-closure-uses.php: added the issue's verbatim closure-with-requirereproducer, plus a closure whose body accesses$$name.tests/PHPStan/Rules/Classes/data/unused-constructor-parameters-include.php: added a constructor whose body accesses$$name.Both callers of the shared check —
UnusedClosureUsesRuleandUnusedConstructorParametersRule— are covered by the single fix location.Root cause
UnusedFunctionParametersCheckwalks the function/closure body collecting the names of variables that are "used", then flags any parameter/usevariable that never appears. Constructs that can access variables PHPStan cannot enumerate statically must short-circuit this walk and mark every in-scope variable as used.include/require/evalwere handled; aVariablewith a dynamic (non-string) name — i.e. a variable variable — was not, so it fell through to the recursive descent, marking only the inner name expression ($name) as used and leaving the actually-captured variable ($container) reported as unused.Test
UnusedClosureUsesRuleTest::testUnusedClosureUses— extended with therequirereproducer from the issue (no error expected) and a$$nameclosure body; the latter fails without the fix withAnonymous function has an unused use $container.and passes with it.UnusedConstructorParametersRuleTest::testParameterUsedInIncludedFile— extended with a constructor accessing$$name; fails without the fix withConstructor of class ... has an unused parameter $usedViaVariableVariable.and passes with it.Fixes phpstan/phpstan#13960