Skip to content

Treat all in-scope variables as used when a closure or constructor body contains include/require/eval#6056

Merged
staabm merged 2 commits into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-jfhwlhc
Jul 16, 2026
Merged

Treat all in-scope variables as used when a closure or constructor body contains include/require/eval#6056
staabm merged 2 commits into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-jfhwlhc

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

PHPStan reported Anonymous function has an unused use $container for a closure whose only use of the captured variable happens inside an include/required file. Because the included file executes in the closure's scope, it can reference any captured variable, so the use is not actually unused. This is a common pattern (used in phpstan-src itself, CommandHelper::executeBootstrapFile()) to isolate the include scope from surrounding locals.

The fix teaches UnusedFunctionParametersCheck that an include/require/eval in the body may consume any in-scope variable, so no unused-use / unused-parameter error should be reported for them.

Changes

  • src/Rules/UnusedFunctionParametersCheck.php: in getUsedVariables(), when a Node\Expr\Include_ (include/include_once/require/require_once) or Node\Expr\Eval_ node is encountered, return $scope->getDefinedVariables() — the same short-circuit already used for func_get_args(), get_defined_vars() and compact().
  • tests/PHPStan/Rules/Functions/data/unused-closure-uses.php + test: added closures capturing $container used only via require_once, include and eval.
  • tests/PHPStan/Rules/Classes/data/unused-constructor-parameters-include.php + test: added a constructor whose parameter is used only inside a require_onced file (the analogous constructor.unusedParameter rule, which shares the same check).

Root cause

getUsedVariables() walks the AST collecting referenced variable names, and a variable used only inside an included file or eval'd string is invisible to that walk. The established remedy for constructs that can reference arbitrary in-scope variables (func_get_args, get_defined_vars, compact) is to return all defined variables so nothing is flagged. include/require and eval belong to that same family and were missing.

The pattern affects both rules built on UnusedFunctionParametersCheck:

  • UnusedClosureUsesRule (closure.unusedUse)
  • UnusedConstructorParametersRule (constructor.unusedParameter)

Both are fixed by the single change since they share getUsedVariables().

Test

  • UnusedClosureUsesRuleTest: closures capturing $container and using it only through require_once, include and eval no longer report closure.unusedUse. Verified the test fails before the fix.
  • UnusedConstructorParametersRuleTest::testParameterUsedInIncludedFile: a constructor parameter used only inside a require_onced file no longer reports constructor.unusedParameter. Verified this analogous case also failed before the fix and passes after.

Fixes phpstan/phpstan#14962

@staabm
staabm requested a review from VincentLanglet July 16, 2026 12:00
@staabm
staabm force-pushed the create-pull-request/patch-jfhwlhc branch from 9a70b75 to d8786c0 Compare July 16, 2026 12:05
@staabm

staabm commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

remaining CI errors are unrelated

edit: fixed in #6057

staabm and others added 2 commits July 16, 2026 15:10
…dy contains `include`/`require`/`eval`

- `UnusedFunctionParametersCheck::getUsedVariables()` now returns `$scope->getDefinedVariables()` when it encounters a `Node\Expr\Include_` (include/include_once/require/require_once) or `Node\Expr\Eval_` node, mirroring the existing handling for `func_get_args()`, `get_defined_vars()` and `compact()`.
- This suppresses the false-positive "Anonymous function has an unused use $x" (closure.unusedUse) when the captured variable is only referenced inside an included file or eval'd code — a common pattern for scoping include/require away from local variables.
- The same fix automatically covers the analogous "Constructor has an unused parameter" (constructor.unusedParameter) rule, since both rules share this check.
@staabm
staabm force-pushed the create-pull-request/patch-jfhwlhc branch from 5a644cf to 0ec524f Compare July 16, 2026 13:10
@staabm
staabm merged commit 501ac99 into phpstan:2.2.x Jul 16, 2026
736 of 738 checks passed
@staabm
staabm deleted the create-pull-request/patch-jfhwlhc branch July 16, 2026 13:26
ondrejmirtes added a commit that referenced this pull request Jul 16, 2026
The branch answers invalidation checks from the per-holder index of
contained node keys in MutatingScope, so the extracted
invalidateExpressionEntries/shouldInvalidateExpression/
containsExpressionToInvalidate/buildTypeSpecifications helpers have no
callers left. Also drop the baseline entries for the unused-use closure
errors that upstream's include/require handling (#6056) resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VktvX3FRdnSsL66xGtiUh8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Anonymous function has an unused use $container" and include/require

3 participants