Skip to content

Price op-assigns as binary ops when deriving conditional expressions - #6777

Merged
ondrejmirtes merged 2 commits into
2.3.xfrom
perf-15498
Oct 22, 2026
Merged

ondrejmirtes merged 2 commits into
2.3.xfrom
perf-15498

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Closes phpstan/phpstan#15498

Propagating conditional expressions through $var op= value (AssignHandler::processDerivedConditionalExpressionsAfterAssign) re-priced the AssignOp itself under every evaluated holder. On 2.3.x that is an on-demand walk of the whole op-assign including its write, which derives conditional expressions again on the evaluation scope — nested, so time grew exponentially with the number of conditionally described variables the right-hand side reads. 2.2.x only asked for the type.

The value an op-assign writes is $var op value, so the equivalent BinaryOp is priced instead. Inferred types are unchanged; the PHP twin and the turbo mirror carry the same change (walk-trace identical).

Reproducer from the issue (added as tests/bench/data/bug-15498.php), user CPU, -l 8 --debug, empty file ≈ 0.8 s:

accumulators 2.3.x before after 2.2.17
6 6.0 s 1.1 s 0.7 s
7 34 s 1.0 s 0.7 s
26 does not finish 1.1 s 0.8 s

tests/PHPStan/Analyser/nsrt/bug-15498.php pins derived narrowing through .=, += and ??= plus the reproducer's types.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L1wYnCXeqv8tgY2Eq1UuZA

ondrejmirtes and others added 2 commits October 22, 2026 21:46
Propagating conditional expressions through `$var op= value` re-priced
the AssignOp itself under every evaluated holder. That walks the write
again, which derives conditional expressions again on the evaluation
scope - nested, so analysis time grew exponentially with the number of
conditionally described variables the right-hand side reads (several
int|float accumulators concatenated with .= in a loop). The value the
op-assign writes is `$var op value`, so the equivalent BinaryOp is priced
instead, without the write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1wYnCXeqv8tgY2Eq1UuZA
@ondrejmirtes
ondrejmirtes merged commit 59e1738 into 2.3.x Oct 22, 2026
267 of 269 checks passed
@ondrejmirtes
ondrejmirtes deleted the perf-15498 branch October 22, 2026 19:47
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.

Exponential analysis time with several int|float accumulators concatenated into a string in a loop (regression in 2.3.0)

1 participant