Skip to content

Fix phpstan/phpstan#6830: Variable inside loop might not be defined.#5213

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-i86p0d6
Open

Fix phpstan/phpstan#6830: Variable inside loop might not be defined.#5213
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-i86p0d6

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When a variable is conditionally assigned before a foreach loop and then used inside the loop under the same condition, PHPStan incorrectly reports "Variable $x might not be defined." This fix preserves conditional expression knowledge across loop iterations even when the variable's type evolves due to reassignment.

Changes

  • Modified intersectConditionalExpressions() in src/Analyser/MutatingScope.php to fall back to condition-based matching when exact key matching fails, merging result type holders via and()
  • Added conditionExpressionTypeHoldersEqual() private helper method for comparing condition parts of conditional expressions
  • Added regression test in tests/PHPStan/Rules/Variables/data/bug-6830b.php and corresponding test method in tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Root cause

intersectConditionalExpressions() matched conditional expression holders by their full key, which includes both the condition (e.g., $do=true) and the result type+certainty (e.g., int(9999) (Yes)). During foreach loop iterations, when mergeWith() is called to combine the loop body scope with the original scope, the result type of a variable can change due to reassignment in the loop body (e.g., from 9999 to 123). This caused the keys to differ, dropping the conditional expression entirely. On subsequent iterations, PHPStan no longer knew the variable was defined when the condition held.

The fix adds a fallback: when no exact key match is found, it searches for a conditional expression with matching conditions (same guard expressions and types). If found, it merges the result type holders using ExpressionTypeHolder::and(), which unions the types and ANDs the certainties. The exact key match is kept as a fast path for the common case.

Test

Added testBug6830b in DefinedVariableRuleTest with a test case that assigns a variable conditionally before a foreach loop, then uses and reassigns it inside the loop under the same condition. The test expects no errors (empty expected errors array).

Fixes phpstan/phpstan#6830

- Modified intersectConditionalExpressions() in MutatingScope to match
  conditional expressions by their condition part when exact key match
  fails, then merge result type holders via and()
- Added conditionExpressionTypeHoldersEqual() helper method
- New regression test in tests/PHPStan/Rules/Variables/data/bug-6830b.php
- Root cause: conditional expression keys include result types, which
  change across loop iterations when variables are reassigned, causing
  the intersection to drop them entirely
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.

2 participants