Skip to content

Fix while/for loop exit scope for conditions with pre/post increment/decrement#5687

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

Fix while/for loop exit scope for conditions with pre/post increment/decrement#5687
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-zd89xvb

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

  • Fix while(++$counter < 100) reporting counter as *NEVER* after loop exit instead of int<100, max>
  • Same fix applied to for(; ++$counter < 5;) pattern
  • Also handles PostInc, PreDec, and PostDec in the same position

Root cause

When a loop condition like ++$counter < 100 is processed:

  1. The truthy scope narrows $counter to int<1, 99> for the loop body
  2. This narrowed type flows through the body unchanged
  3. At loop exit, filterByFalseyValue(++$counter < 100) tries to subtract int<min, 99> from the body's int<1, 99>, producing *NEVER*

The contradiction occurs because filterByFalseyValue doesn't re-evaluate the increment side effect -- it only narrows the existing (already-narrowed) type.

Fix

When the condition is a direct comparison with an increment/decrement operand, use condResult->getFalseyScope() instead. This scope correctly reflects both the side effect and the falsey narrowing, evaluated on the merged/generalized loop scope. For all other condition forms (compound &&/||, simple comparisons), the old filterByFalseyValue approach is preserved to maintain precision from body-output types.

Test plan

  • Regression test in tests/PHPStan/Analyser/nsrt/bug-7230.php covering PreInc, PostInc, PreDec in both while and for loops
  • Existing while-loop-variables.php test passes (compound && condition with PostInc)
  • Existing for-loop-variables.php test passes (standard for loop patterns)
  • Existing integer-range-types.php test passes (in-loop assertions)
  • Full test suite passes (12078 tests)
  • PHPStan self-analysis passes with no errors
  • CS check passes

Closes phpstan/phpstan#7230

…ect inc/dec

When a while or for loop condition is a comparison with a direct
PreInc/PreDec/PostInc/PostDec operand (e.g. `while(++$counter < 100)`),
the old approach of applying filterByFalseyValue on the body output scope
produced NEVER for the counter variable. This happened because the body
scope inherited the truthy-narrowed type from condition evaluation, and
the falsey filter then contradicted it by subtracting the same range.

The fix uses condResult->getFalseyScope() in this case, which correctly
captures both the side effect (increment/decrement) and the falsey
narrowing on the pre-body merged scope. For conditions without direct
inc/dec operators, the old filterByFalseyValue approach is preserved to
maintain body-output precision for other variables.

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