Repository navigation
Cut memory and time of analysing large array literals - #6778
Merged
Merged
Conversation
A literal's type is lexical - the same on every scope and in both flavours - and literals are never tracked in a scope, so a lazy typeCallback bought nothing. It cost a closure (~800 B) capturing an InitializerExprContext (~160 B) for every literal, about three times the ConstantStringType it eventually produced. That adds up in large array literals: ArrayHandler keeps every item's ExpressionResult alive until the array's own type is resolved, so a 20,000-entry string-keyed literal held ~38 MB of those closures and contexts at the peak. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017pydsEaJ8dAiZ22Nt1Yqf4
Every explicit key was searched for with array_search() in the list of keys seen so far, making the rule quadratic in the number of items - a 40,000-entry literal spent 3.3 s in the rule alone. The seen keys are now indexed by their type and value, which keeps the lookup as strict as the identity comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017pydsEaJ8dAiZ22Nt1Yqf4
ondrejmirtes
force-pushed
the
issue-15452
branch
from
October 22, 2026 19:48
a0211cb to
acf2dc3
Compare
This was referenced Oct 22, 2026
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.
Analysing a 20,000-entry string-keyed array literal took 139 MB and 1.46 s; 40,000 entries took 288 MB and 4.48 s. Two independent causes:
Memory — literal types were held behind a closure.
ArrayHandlerkeeps every item'sExpressionResultalive until the array's own type is resolved (each item type is captured at its own evaluation point). Every literal's result carried a lazytypeCallbackclosure (~800 B) capturing anInitializerExprContext(~160 B) — about three times theConstantStringTypeit eventually produced, which rules ask for anyway. A literal's type is lexical and literals are never tracked in a scope (isSpecifyExpressionTypeNoop()is true forScalar), soScalarHandlernow resolves the type eagerly. Ported toturbo-ext/src/ScalarHandler.cpp.Time —
DuplicateKeysInLiteralArraysRulewas quadratic. Each explicit key was looked up witharray_search()in the list of seen keys; at 40,000 entries the rule alone took 3.3 s. Seen keys are now indexed by type and value, keeping the lookup as strict as the identity comparison.Reporter's reproducer (https://github.com/alfredbez/phpstan-literal-array-memory-reproducer), turbo off:
Most of what remains at 20,000 entries is the parsed AST of the 700 KB file (~56 MB) and one
ExpressionResultper key and value.tests/bench/data/bug-15452.phpis a 5,000-entry fixture produced by the reporter's generator (~0.43 s → ~0.27 s, 28 → 24 MB).Closes phpstan/phpstan#15452
🤖 Generated with Claude Code
https://claude.ai/code/session_017pydsEaJ8dAiZ22Nt1Yqf4