Skip to content

Contextual typing fails when discriminant is an interpolated template literal #53888

@jcalz

Description

@jcalz

Bug Report

🔎 Search Terms

discriminated union, discriminant, interpolated template literal, contextual typing, delayed, deferred

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about discriminated unions

⏯ Playground Link

Playground link with relevant code

💻 Code

type S = { d: "s"; cb: (x: string) => void; };
type N = { d: "n"; cb: (x: number) => void; };
declare function foo(foo: S | N): void;
foo({ d: "n", cb: x => x.toFixed() }); // okay
foo({ d: "s", cb: x => x.toUpperCase() }); // okay 
foo({ d: `${"s"}`, cb: x => x.toXXX() }); // error!
// ------------------> ~
// Parameter 'x' implicitly has an 'any' type.

🙁 Actual behavior

In the third call to foo(), the discriminant property d is an interpolated template literal whose type is correctly seen to be "s", but the cb property's callback parameter x is not contextually typed as string and instead implicitly falls back to any.

🙂 Expected behavior

The third call to foo() should behave just like the second call to foo(), where the object is narrowed to S and therefore cb's parameter is contextually typed as string.

Notes

Comes from this Stack Overflow question

I'm imagining this is just a design limitation where the type of d is computed too late for it to be available when the contextual typing for cb happens. A similar failure occurs when the discriminant is the output of an inline function call:

function justS(): "s" { return "s" };
foo({ d: justS(), cb: x => x.toXXX() }); // error!
// -----------------> ~
// Parameter 'x' implicitly has an 'any' type.

Playground link

I searched around for an existing issue but I didn't find an exact enough match. There's #35769 / #41759 / #46847, for example... related but not obviously the same.

The obvious workaround is just to do the interpolation ahead of time into a const and use that instead:

const s = `${"s"}` as const;
foo({ d: s, cb: x => x.toUpperCase() }); // okay

Playground link

Mostly I'm just looking for an official status on this (and expecting it to be Design Limitation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions