fix(#1068): preserve generic T when curried component is passed to another generic component#1
Draft
johanrd wants to merge 1 commit into
Draft
fix(#1068): preserve generic T when curried component is passed to another generic component#1johanrd wants to merge 1 commit into
johanrd wants to merge 1 commit into
Conversation
…assed to another generic component
When {{#let (component Cell onSelect=@onselect) as |BoundCell|}} is
used and BoundCell is passed to another generic component like
<Row @BoundCell={{BoundCell}}>, TypeScript's higher-order inference
preserves Cell's generic T as an independent type parameter, causing
the consumer's T to default to the constraint instead of the enclosing
scope's abstract T.
Fix: ScopeStack tracks bind-invokable block params from {{#let}} via
ref-counted map. When these are used as args to other components,
they're cast to `any` to prevent the independent generic from
overriding T inference. Type safety is maintained by the keyword's
validation in the comma expression's first stage.
NullVoxPopuli-ai-agent
pushed a commit
to NullVoxPopuli-ai-agent/glint
that referenced
this pull request
Jul 4, 2026
…consumer inference
A generic class component curried via {{component}} keeps its own type
parameter free in the emitted bindInvokable result -- deliberately, since
that is what lets the curried value satisfy generic-shaped targets like
WithBoundArgs<typeof C, K> (typically reached via {{yield}}). But when a
{{#let}}-bound alias of such a value was passed as an ARG to another generic
component, TypeScript instantiated the free generic to its constraint during
inference, and that collapsed candidate beat (or conflicted with) the correct
candidates from sibling args: in the issue's DataTable -> Row -> Cell chain,
Row's T became Identifiable instead of the enclosing T.
No library-signature shape can express "pin the curried generic from the
bound args" -- TypeScript only instantiates a generic source signature in
context when the target signature is otherwise fully concrete (several
attempted shapes are documented in the PR). Instead, the transform now
tracks {{#let}} block params initialized from bind-invokable subexpressions
with named args (shadow-correct, via ScopeStack binding metadata) and casts
exactly those references, in arg position only, to the new
InferenceInertInvokable type: the value contributes no inference candidates
while remaining an Invokable, and every other position (notably {{yield}})
keeps the fully-typed reference.
Trade-off: at those arg positions, compatibility between the curried
component's signature and the consuming arg's declared type is not checked.
Based on the approach explored in johanrd#1, with a bounded cast
target instead of `any`, shadow-correct scope tracking, and JSDoc-cast
support for untyped scripts.
Co-Authored-By: Claude Fable 5 <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.
A possible direction for typed-ember#1068 (comment)
When {{#let (component Cell onSelect=@onselect) as |BoundCell|}} is used and BoundCell is passed to another generic component like <Row @BoundCell={{BoundCell}}>, TypeScript's higher-order inference preserves Cell's generic T as an independent type parameter, causing the consumer's T to default to the constraint instead of the enclosing scope's abstract T.
Fix: ScopeStack tracks bind-invokable block params from {{#let}} via ref-counted map. When these are used as args to other components, they're cast to
anyto prevent the independent generic from overriding T inference. Type safety is maintained by the keyword's validation in the comma expression's first stage.Written by claude