Skip to content

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

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

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

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When a variable is conditionally defined before a loop and used inside the loop under the same condition, PHPStan incorrectly reports "Variable $x might not be defined." This happens because the conditional expression tracking ("if $do is true then $x is defined") is lost during loop scope merging when the variable's type changes across iterations.

Changes

  • Modified MutatingScope::intersectConditionalExpressions() in src/Analyser/MutatingScope.php to add a fallback merging path when exact key matching fails
  • When two conditional expression holders have the same conditions (e.g., $do=true) but different result types (e.g., int(9999) vs int(123)), they are now merged by taking the union of result types and the minimum certainty
  • Extended regression test in tests/PHPStan/Rules/Variables/data/bug-6830.php with the original issue's reproduction case (variable defined before loop, used inside loop)

Root cause

intersectConditionalExpressions() uses ConditionalExpressionHolder::getKey() for matching, which includes the result type in the key string (e.g., $do=true => 9999 (Yes)). When a variable's type changes across loop iterations (e.g., $x goes from 9999 to 123), the keys differ and the intersection drops the conditional expression entirely. This means the knowledge that "$do being true implies $x is defined" is lost, causing the false positive.

The fix adds a fallback: when exact key matching fails, it matches holders by their condition expressions only, then merges the result types using TypeCombinator::union() and takes the minimum certainty via TrinaryLogic::maxMin().

Test

Extended the existing tests/PHPStan/Rules/Variables/data/bug-6830.php with two additional test functions:

  • test2(): reproduces the original issue — variable $x defined conditionally before a foreach loop, reassigned inside the loop under the same condition
  • test3(): simpler variant — variable $x defined conditionally before a foreach loop, read inside the loop under the same condition

Both expect no errors (previously test2 reported "Variable $x might not be defined" on line 28).

Fixes phpstan/phpstan#6830

- Fixed intersectConditionalExpressions() in MutatingScope to merge conditional
  expression holders with same conditions but different result types
- Previously, when a variable was conditionally defined before a loop and used
  inside the loop under the same condition, the conditional expression was lost
  during scope merging because the result type changed across iterations
- Added fallback logic: when exact key matching fails, match holders by their
  condition expressions and merge result types via TypeCombinator::union()
- Extended regression test in tests/PHPStan/Rules/Variables/data/bug-6830.php
@staabm staabm closed this Mar 4, 2026
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