Skip to content

Fix #11545: while-true-if-break: Variable might not be defined#4974

Open
phpstan-bot wants to merge 7 commits into2.1.xfrom
create-pull-request/patch-9g5a84g
Open

Fix #11545: while-true-if-break: Variable might not be defined#4974
phpstan-bot wants to merge 7 commits into2.1.xfrom
create-pull-request/patch-9g5a84g

Conversation

@phpstan-bot
Copy link
Collaborator

@phpstan-bot phpstan-bot commented Feb 17, 2026

Summary

When a loop with an always-true condition (while(true), for(;;), do...while(true)) exits only through a break inside an if block, variables assigned before the break were incorrectly reported as "might not be defined" after the loop.

Changes

  • Modified while loop handling in src/Analyser/NodeScopeResolver.php (around line 1519): when $alwaysIterates is true and there are break exit points, build $finalScope purely from break scopes instead of merging them into the unreachable body-end scope
  • Modified do-while loop handling in src/Analyser/NodeScopeResolver.php (around line 1636): same fix applied
  • Modified for loop handling in src/Analyser/NodeScopeResolver.php (around line 1758): same fix for break scope construction, plus skip merging with pre-loop scope when $alwaysIterates is true
  • Added regression test in tests/PHPStan/Rules/Variables/data/bug-11545.php covering while(true), for(;;), and do...while(true) variants
  • Added test method testBug11545 in tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Root cause

When processing loops, PHPStan constructs a $finalScope representing the state after the loop exits. For while(true), this starts with $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond) — the scope where the condition true is falsey, which is effectively unreachable. Break exit point scopes were then merged into this unreachable scope using mergeWith(). The mergeWith() method treats a variable existing in one scope but not the other as "maybe defined" (TrinaryLogic::Maybe). Since the unreachable scope doesn't have the variable $result, merging produces "maybe" certainty instead of "yes".

The fix recognizes that when a loop always iterates, the only way to reach post-loop code is through a break statement. Therefore, the post-loop scope should be constructed entirely from the break exit point scopes, not merged with the unreachable body-end scope.

Test

Added tests/PHPStan/Rules/Variables/data/bug-11545.php with three functions testing while(true), for(;;), and do...while(true) loops where the only exit is a break inside an if block that also assigns a variable. The test expects zero errors, confirming the variable is considered definitely defined after the loop.

Fixes phpstan/phpstan#11545

Closes phpstan/phpstan#10245
Closes phpstan/phpstan#11984
Closes phpstan/phpstan#5477
Closes phpstan/phpstan#5919
Closes phpstan/phpstan#9023

phpstan-bot and others added 7 commits February 17, 2026 03:27
- When a loop always iterates (while(true), for(;;), do...while(true)),
  the only way to exit is through a break statement
- Previously, break scopes were merged into an unreachable "condition
  became false" scope, causing variables defined before break to be
  reported as "might not be defined"
- Now, when the loop always iterates and has break exit points, the
  post-loop scope is built purely from the break exit point scopes
- Applied the fix consistently to while, for, and do-while loops
- Added regression test for all three loop types

Closes phpstan/phpstan#11545
Automated fix attempt 1 for CI failures.
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.

1 participant