Skip to content

Contextual narrowing fails inside thunked argument when T is inferred from a separate argumentΒ #63511

Description

πŸ”Ž Search Terms

thunk inference contextual narrowing
arrow function return contextual typing
nested array literal widening
generic inference across arguments
callback return literal narrowing
NoInfer (unrelated β€” same bug without it)

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried (4.9.5, 5.5.4, 5.9.3, 6.0.0-dev.20260416), and I reviewed the FAQ.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=6.0.0-dev.20260416#code/C4TwDgpgBAwg9gGzgJygXigcmRAJpqAHywCMEBXCTAbgChRJYBDZZASwmQB4AVAPnRQA3lAD6o4AH4AXFAAUAD1k8AlOgEA3OG1xQAvnVq4IAYwQtoAM3IA7E8DZwbUXGxz3efOSdkwW7Tk8AGig4MGAAZ1kRE0QUKKgcJlwnBBAoHgBtAF19FWU6YzMLKGs7BycoYAALWwBrPE9vX38Obn4QsMjZOTU0ARi45ASklJs0jJy8gtpaWJsI4CgTQRtyBAQoJgioeps4AHdnbeZWNq54JGQ+Q1d3YG8QwauEzOw8TFy9FWooP-+AVAAPRAqCAUHIoDZ-IcEjxBJcUPRajYGrhHvI+gI5M94rI3jh8F8VD8-iCoIAZcigi3YNgA5lN9ktthE2LSoWRoMA4IkIMlUukEcgckA

πŸ’» Code

type Color = 'red' | 'blue';
type Carrier<T> = { __t?: (x: T) => void };

declare function direct<T>(c: Carrier<T>, opts: { colors: readonly T[] }): T;
declare function thunked<T>(c: Carrier<T>, opts: () => { colors: readonly T[] }): T;

const c = null as unknown as Carrier<Color>;

direct(c, { colors: ['red'] });           // βœ… narrows: T = Color
thunked(c, () => ({ colors: ['red'] }));  // ❌ string[] not assignable to readonly Color[]

πŸ™ Actual behavior

T is inferred as Color from Carrier<T> in both calls. In the thunked form the nested array literal ['red'] widens to string[] before contextual typing reaches it from readonly T[], producing Type 'string' is not assignable to type 'Color'.

πŸ™‚ Expected behavior

The thunked form narrows the same way as direct: ['red'] is contextually typed against readonly Color[] and the call type-checks. The two forms differ only in whether the options are bound directly or via an arrow function β€” the inferred T is the same.

Additional information about the issue

The pattern matters for reactive-resource wrapper libraries (e.g. glimmer-apollo) that require args to be wrapped in a function so they can re-evaluate when tracked state changes. The thunk shape is idiomatic; the inference asymmetry forces consumers to scatter as const casts across nested literals.

Possibly related, but verified not the same bug:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions