Generalize an inferred template type by what a conditional branch knows about it - #6607
Open
SanderMuller wants to merge 1 commit into
Open
SanderMuller wants to merge 1 commit into
SanderMuller wants to merge 1 commit into
Conversation
…ws about it In the else branch of `(TNewKey is \UnitEnum ? array-key : TNewKey)` with TNewKey of array-key|\UnitEnum, the branch knows TNewKey is an array-key. Since the branches hold NarrowedSubjectType references, the resolver traversed into them and generalized the inferred type against the subject's own bound, which is not scalar, so a literal key such as 'foo' became string. 2.2.14 kept 'foo'. The resolver now resolves the subject inside a NarrowedSubjectType without generalizing it, and generalizes the narrowed result against the reference, whose bound is the narrowed one. A template type in the target stays generalized, as the condition sees it: otherwise `(T is U ? T : int)` narrowed 'foo' & 'bar' to never. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SanderMuller
force-pushed
the
narrowed-subject-generalization
branch
from
September 26, 2026 22:06
435aada to
e8528b0
Compare
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.
laravel/framework's type tests fail in the integration suite since 2.2.15, on 2.2.x and 2.3.x. The job named "Integration - laravel/framework" appears twice in the checks. The
laravel.neonrun passes and thelaravel-types.neonrun fails, so it can look like a flaky job. It is not: I get the same 6 errors on every run. At the pinned laravel commit, the 2.2.14 phar reports 0 errors, 2.2.15 reports 7 and 2.2.16 reports 6.It bisects to 91dcf7e from #6480. A reduced version of laravel's
keyBy():Since #6480 the else branch holds a
NarrowedSubjectTypeforTNewKey.resolveResolvableTemplateTypes()traverses into it and generalizes the inferred'foo'againstTNewKey's own bound,array-key|\UnitEnum. That bound is not scalar, sogeneralizeInferredTemplateType()widens'foo'tostring. In 2.2.14 the branch heldTNewKey ~ \UnitEnum, with boundint|string, and the same function kept'foo'.The fix resolves the subject inside a
NarrowedSubjectTypewithout generalizing it. It then generalizes the narrowed result against the reference, whose bound and variance are the narrowed ones.The resolver is now a private method, not a closure. A closure that calls itself through
use (&$resolve)is a reference cycle, and undergc_disable()it leaked 20 MB on thebug-13352bench. As a method, the bench peaks at 187.3 MB, the same as 2.2.x. The time is within noise: 1.485s and 1.487s, against 1.49s to 1.52s on 2.2.x.Only the subject is kept as inferred. A template type in the target, as in
(T is U ? T : int), is still generalized, because the condition compares the generalized types. Without that,'foo' & 'bar'narrowed to never and the call reported an unresolvable return type.nsrttest fails without the change on five assertions, the three literal keys and the two template targets, and passes with it. The enum assertions pass either way.make tests,make phpstanand phpcs are green.laravel-types.neonanalysis at the pinned commit goes from 6 errors to 0.dumpType()cases the result equals 2.2.14 in 74. Three of the other 6 equal 2.2.16. The last 3 are more precise than both releases. For example,(T is 'foo' ? T : int)called with'bar'now givesint, where 2.2.14 gave'bar'|intand 2.2.16'foo'|int.On 2.3.x,
ResolvedFunctionVariantWithOriginalhas a turbo mirror, so the merge-up needs the same change in C++.The other caller of
generalizeInferredTemplateType(),GenericTypeTemplateTraverser, skips aNarrowedSubjectTypethe same way. It maps only a class's own template types fornew, and it returns a default or a bound without traversing into it. So I do not see how aNarrowedSubjectTypecould reach it, and I left it alone. I checked this by reading the code, not with a test.🤖 Generated with Claude Code