Skip to content

Keep only the current conditionals a replayed statement's recorded walk kept - #7075

Open
ondrejmirtes wants to merge 1 commit into
2.3.xfrom
keep-current-conditionals-in-replayed-statement
Open

ondrejmirtes wants to merge 1 commit into
2.3.xfrom
keep-current-conditionals-in-replayed-statement

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Closes phpstan/phpstan#15736

When a loop's final walk replays a statement from the previous convergence pass (because it mentions no variable whose state changed), MutatingScope::withRecordedStatementDelta() copied the recorded exit's whole conditional-holder list for every expression whose holders the statement changed. If the current holders of that expression differed from the recorded entry's, the copied ones described the previous pass's state.

In the issue's snippet, $rowCount++ drops the $values holders guarded by $rowCount, so the replay replaced the final walk's $values holders with the pass's, including "$cols is int<1, max> implies $values is array{0?: ...}". Since 2a70999 the while loop's merge with its not-ran path keeps that holder ($cols is 0 there), and $col = 0 in the later for loop then narrowed $values to array{0?: ...}.

Such an expression now keeps only its current holders that the recorded walk kept too. Dropping holders can only lose precision; when the current holders equal the recorded entry's, the delta is applied exactly as before. The native mirror in turbo-ext/src/MutatingScope.cpp gets the same change.

The regression test is a separate TypeInferenceTestCase without bleeding edge: with bleeding edge on, ConstantArrayType builds the loop's shapes differently and the bug does not reproduce.

…lk kept

A statement replayed from a loop's previous pass carried the recorded exit's
whole holder list onto the current scope for each expression whose
conditional holders the statement changed. When the current holders of that
expression differed from the recorded entry's, the recorded ones described
the previous pass's state: `$rowCount++` dropping the `$values` holders
guarded by `$rowCount` replaced the final walk's `$values` holders with the
pass's, such as `$cols` int<1, max> implying `$values` is array{0?: ...}.
The while loop's merge with its not-ran path then kept that holder, and
`$col = 0` in a later for loop narrowed `$values` to array{0?: ...}.

Such an expression now keeps only its current holders that the recorded
walk kept too.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1deb9-7acb-72e2-bab8-0404b371a021
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.

Array built in a nested while loop becomes array{0?: ...} in a later for loop: "Offset might not exist" (regression in 2.3.x-dev)

1 participant