Repository navigation
Cut recomputation in loop convergence and two on-demand re-walks - #6765
Merged
Merged
Conversation
Each convergence pass after the first compares every statement's entry state with the previous pass's. A statement that mentions no variable whose state differs has its recorded emissions replayed and its recorded effect carried onto the current scope; once the entry state equals the previous pass's, the rest of that pass stands. Variables holding a closure that captures a differing variable by reference count as mentions of it, since invoking the closure reads the variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X
The convergence loops of while, do-while and for now go through processLoopPassStmtNodes() like foreach: a later pass replays the statements whose inputs did not change since the previous pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X
A loop body holding a construct that walks differently at top level than in a convergence pass (a nested loop runs its own convergence only in the final walk) was walked in full by the final walk, although only those statements differ. The passes of such bodies now record their emissions too, and the final walk runs against the last pass like a later pass does: a statement mentioning no variable whose state differs from the pass's entry replays the pass's emissions through the real callback, and statements holding such a construct are walked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X
InvalidBinaryOperationRule priced every binary operation and compound assignment a second time: it cloned the node with both operands replaced by TypeExpr of the types to check and walked that synthetic node on demand - three walks per operation, half of all on-demand walks in self-analysis. When the types to check are the operand types the walk used, the operation's own stored type answers the same question. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X
After a foreach that may not iterate, the entry scope narrowed by `$iteratee === [] || is_object($iteratee)` merges into the exit scope. The condition was built as a synthetic BooleanOr and walked on demand, an is_object() call resolution included - 16K on-demand walks in self-analysis. ForeachEmptyOrObjectNarrowingHelper composes the same narrowing through BooleanNarrowingHelper::specifyDisjunction(): the comparison from the iteratee's result, is_object() from its conditional return type. The walk remains the fallback when a side cannot be composed. The helper runs once per loop exit and stays PHP; the native ForeachHandler calls it. Before switching, both were computed and compared for every narrowing in self-analysis and the test suite: equal scopes throughout, none falling back to the walk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X
The helper ForeachHandler calls after a loop that may not iterate gets its native twin: narrowToEmptyOrObject() builds the disjunction and composes its narrowing through the direct entries of IdenticalNarrowingHelper, DefaultNarrowingHelper, ParametersAcceptor selection and BooleanNarrowingHelper, with the twin's closures as native closures over the same captures. Covered by walk-trace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X
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.
Five changes that cut what the engine recomputes. Each is mirrored in the turbo twins, and the analysis output is identical.
foreach, thenwhile/do-while/for). Each statement is compared with the previous pass's entry state. A statement that doesn't mention a variable whose state changed replays its recorded emissions and carries its recorded effect onto the current scope. Variables holding a closure that captures a changed variable by reference count as mentions of it.InvalidBinaryOperationRulestops re-pricing operations. It walked a synthetic copy of every binary operation and compound assignment, about half of all on-demand walks. When the checked operand types are the walked ones, the operation's stored type answers the question.foreach$it === [] || is_object($it)narrowing is composed throughBooleanNarrowingHelper::specifyDisjunction(), not walked as a synthetic||with anis_object()call resolution. Equivalence was checked before switching: equal scopes for every case in self-analysis and the test suite, none falling back to the walk. The newForeachEmptyOrObjectNarrowingHelperhas a native twin.Benchmark: full parallel self-analysis of phpstan-src, turbo on, ShipMonk DCD disabled locally in both legs, 4 ABBA pairs against 2.3.x
e4f45f9b3e:Verification:
🤖 Generated with Claude Code
https://claude.ai/code/session_01MMNnG2Zo7wgvSZCzjdk17X