TS seems to think that the type of the interpolated prop value is as follows:
{ href: `#234${undefined}2213` }
are not strings (when they are used in distinguishable unions)?
The third p3
instance below loses type inference for the ev
field, but only if the href is an interpolated esliteral string.
type BiomePlainLinkProps = { href: string; onClick?: (event: string) => void; } type BiomeButtonProps = { href?: never; onClick?: (event: number) => void; } export type ClickableDiscriminatedUnion = | BiomePlainLinkProps | BiomeButtonProps; const p1: ClickableDiscriminatedUnion = { href: '2332132', onClick: (ev) => console.log('@@@@', ev), // ev is string here } const p2: ClickableDiscriminatedUnion = { onClick: (ev) => console.log('@@@@', ev), // ev is number here } const p3: ClickableDiscriminatedUnion = { href: `2${undefined}332132`, onClick: (ev) => console.log('@@@@', ev), // ev is any (not string) here }
TS sandbox complete reproduction
Update TS5.2
This bug has been fixed in microsoft/TypeScript#53907, so as of TypeScript 5.2, the code in the question will work as expected without any changes.
Use the Playground link of TS 5.2.0-dev.20230516
Original answer for TS5.1-
It turns outYes, this is a bug in TypeScript, as microsoft/TypeScript#53888, which I submitted in response to a Stack Overflow question. The compiler seems to recognize that the discrimination attribute
Thehref
is of typestring
rather thanundefined
, but this happens too late EnteronClick
callback parameters based on context. Presumably template literal interpolation is enough to delay things causing problems.GitHub issue is in the Backlog, which means the TS team doesn't plan to fix it anytime soon... but therefore it's also marked as Needs Help a>, which means They will accept pull requests from community members; therefore, anyone looking to resolve this issue as soon as possible should consider contributing the fix themselves.
Meanwhile, the workaround I suggest here is to do the string interpolation ahead of time and store it in
const
so that its type is known ahead of time:It's annoying (probably even more annoying for JSX), but at least it works!