Cap cumulative unroll factor when nesting unrolled constant-array foreach#5614
Merged
ondrejmirtes merged 1 commit into2.1.xfrom May 8, 2026
Merged
Cap cumulative unroll factor when nesting unrolled constant-array foreach#5614ondrejmirtes merged 1 commit into2.1.xfrom
ondrejmirtes merged 1 commit into2.1.xfrom
Conversation
…each Closes phpstan/phpstan#14590 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tryProcessUnrolledConstantArrayForeachliterally evaluates the body once per(key, value)pair. With deep nesting the cumulative product explodes — e.g. 9 nestedforeach ([1, 2] as ...)loops causes 2⁹ = 512 inner body evaluations, and a body that appends to an outer array (like$cases[] = $v0) then makes scope merges over a growing constant array compound the cost super-linearly. The per-foreachFOREACH_UNROLL_LIMIT = 16doesn't catch this because each individual loop only has 2 keys.foreachUnrollFactorfield throughStatementContext(getForeachUnrollFactor(),enterUnrolledForeach(int), preserved acrossenterDeep()). When unrolling would push the cumulative product overFOREACH_UNROLL_NESTED_LIMIT = 16, the foreach falls back to the regular non-unrolled analysis path.tests/bench/data/bug-14590.phpso the regression is caught byRegressionBench.Depth-9 reproducer: ~108s → ~3s. PHPStan still cleanly analyses itself; all 12066 existing tests pass.
Closes phpstan/phpstan#14590
Test plan
make tests— all 12066 tests passmake phpstan— no errors