Two-stage collapse for oversized constant arrays#5555
Merged
ondrejmirtes merged 2 commits into2.1.xfrom Apr 27, 2026
Merged
Conversation
Backport of `TypeCombinator` changes from two `unsealed`-branch commits, plus their test updates: - `optimizeConstantArrays` now runs a stage 1 same-key-set collapse before falling back to the lossy generalization. Variants sharing a key signature `mergeWith` losslessly into a single shape; the per-position record structure survives, only the values widen. - `reduceArrays` final pass collapses the loop-accumulator triangular variant pattern (conditional `$xs[] = …` push sites leaving behind list variants of progressively longer length) into a single `non-empty-list<unionValueType>` when their cumulative `countConstantArrayValueTypes` exceeds the limit. Skips when every list variant shares one key signature — those are stage 1's job (per-position precision instead of a flat fold), and on 2.1.x without the unsealed-branch's `getUnsealedTypes()` pre-pass the flat fold would otherwise pre-empt stage 1 and regress bug-7963's 144-record literal. Knock-on effects: - bug-10717: language-code lookup now produces the precise union of every code instead of `bool|literal-string`. - bug-13509: alert variants land on the precise union of seven record shapes instead of the previous `&oversized-array` decomposition. - New nsrt `oversized-array-stages.php` exercises both phases: Phase 1 (small literal preserved as-is) and Phase 2 (eight conditional pushes with same-shape records — the triangular union of list variants pushes the count past the limit, list-collapse folds them into a `non-empty-list` whose value type preserves the per-record `(kind, value, opts)` correlation as a tagged union of the eight original record shapes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes phpstan/phpstan#8636 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
optimizeConstantArraysruns a stage 1 same-key-set collapse before its lossy generalization fallback. Variants sharing a key signaturemergeWithlosslessly into a single shape; per-position record structure survives, only the values widen to a union.reduceArraysfinal pass collapses the loop-accumulator triangular variant pattern (conditional$xs[] = …push sites leaving behind list variants of progressively longer length) into a singlenon-empty-list<unionValueType>when their cumulativecountConstantArrayValueTypesexceeds the limit. Skips when every list variant shares one key signature — those are stage 1's job.Knock-on effects
bool|literal-string.&oversized-arraydecomposition.oversized-array-stages.phpexercises both phases — Phase 1 (small literal preserved as-is) and Phase 2 (eight conditional pushes whose triangular union triggers list-collapse, preserving the per-record(kind, value, opts)correlation as a tagged union of the eight original record shapes).bug-8636(rule test onReturnTypeRule): the playground sample from issue #8636 — a 30-entry HUGE_CONST whose[$c]['p7']lookup previously inferred toarray<int, string>|string— now analyses clean.Closes phpstan/phpstan#8636
Test plan
vendor/bin/phpunit tests/PHPStan/Analyser/NodeScopeResolverTest.php— 1547 / 1547 pass.vendor/bin/phpunit tests/PHPStan/Analyser/AnalyserIntegrationTest.php— 175 / 175 pass (testBug7963 stays green).vendor/bin/phpunit tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php --filter testBug8636— passes; TDD-validated to fail whenTypeCombinator.phpis restored to pre-fix state.🤖 Generated with Claude Code