Skip to content

Fix #5919: Only one possible (always entered) loop exit path is not detected#5072

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

Fix #5919: Only one possible (always entered) loop exit path is not detected#5072
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-i3z5t0o

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When a variable is assigned inside a try block before a break in an always-iterating loop (while(true), do...while(true), for(;;)), PHPStan incorrectly reported "Variable $s might not be defined" after the loop. Since the loop condition is always true, the only way to exit the loop is through break, and at that point the variable is always defined.

Changes

  • Modified while loop handling in src/Analyser/NodeScopeResolver.php (around line 1522): When $alwaysIterates is true and there are break exit points, build the final scope exclusively from break exit point scopes instead of merging them with the unreachable "condition becomes false" scope.
  • Applied the same fix to do...while loop handling (around line 1637).
  • Applied the same fix to for loop handling (around line 1757), also skipping the post-loop scope merge with the pre-loop scope when $alwaysIterates.
  • Added regression test in tests/PHPStan/Rules/Variables/data/bug-5919.php covering all three loop types.
  • Added test method testBug5919 in tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php.

Root cause

When processing a while(true) loop containing try { $s = queryApi(); break; } catch (\Exception $e) { ... }, the scope after the loop was computed by merging two sources:

  1. The "normal flow" scope from $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond) — representing the unreachable path where the loop condition becomes false. This scope came from the catch block where $s was NOT defined.
  2. The break exit point scope — where $s WAS defined.

Merging these two scopes degraded $s from definitely-defined (Yes) to maybe-defined (Maybe), triggering the false positive. The fix recognizes that when the loop always iterates, the "condition becomes false" path is unreachable, so only break exit point scopes should contribute to the final scope after the loop.

Test

Added tests/PHPStan/Rules/Variables/data/bug-5919.php with three test functions covering while(true), do { } while(true), and for(;;) loops, each containing a try/catch where a variable is assigned and then the loop is broken out of. The test expects zero errors (no false positives).

Fixes phpstan/phpstan#5919

- In while(true), do...while(true), and for(;;) loops, when the only exit
  path is through break, the scope after the loop should come exclusively
  from break exit points, not from the unreachable "condition becomes false" path
- Previously, the normal-flow scope (e.g. from a catch block where the variable
  was not assigned) was merged with break exit point scopes, degrading variable
  certainty from Yes to Maybe
- New regression test in tests/PHPStan/Rules/Variables/data/bug-5919.php

Closes phpstan/phpstan#5919
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