Skip to content

Resolve template defaults referencing other templates in composite types - #6329

Open
calebdw wants to merge 1 commit into
phpstan:2.2.xfrom
calebdw:calebdw/push-ktxvvktqmorp
Open

Resolve template defaults referencing other templates in composite types#6329
calebdw wants to merge 1 commit into
phpstan:2.2.xfrom
calebdw:calebdw/push-ktxvvktqmorp

Conversation

@calebdw

@calebdw calebdw commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hello!

ClassReflection::typeMapFromList() only substituted a sibling template reference when the default was exactly a bare TemplateType. So @template TResult = TRelated resolved correctly, but a default that merely contained the reference - = TRelated|null, = list<TRelated>, or an array shape - kept the unresolved template and leaked it into inferred types.

Traverse the type instead, substituting any nested reference to a template of the same class. Bounds are deliberately not descended into, because they may be self-referential (@template T of Foo<T>) and traversing them expands without bound.

The substitution has to apply to explicitly passed arguments as well as to $tag->getDefault(), because TypeNodeResolver already pads missing arguments with the raw defaults before this code runs.

Thanks!

`ClassReflection::typeMapFromList()` only substituted a sibling template
reference when the default was exactly a bare `TemplateType`. So
`@template TResult = TRelated` resolved correctly, but a default that
merely contained the reference - `= TRelated|null`, `= list<TRelated>`,
or an array shape - kept the unresolved template and leaked it into
inferred types.

Traverse the type instead, substituting any nested reference to a
template of the same class. Bounds are deliberately not descended into,
because they may be self-referential (`@template T of Foo<T>`) and
traversing them expands without bound.

The substitution has to apply to explicitly passed arguments as well as
to `$tag->getDefault()`, because TypeNodeResolver already pads missing
arguments with the raw defaults before this code runs.
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.

1 participant