Repository navigation
Closed
Labels
Working as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Description
Bug Report
Some of my code using Extract stopped working when I upgraded from 5.0 to 5.1. This may be related to #54676. I didn't see anything in the release announcement to suggest that this might happen.
🔎 Search Terms
Extract, TypeScript 5.1, discriminated union
🕗 Version & Regression Information
- This changed between versions 5.0.4 and 5.1.3
⏯ Playground Link
💻 Code
type A = {
type: "A";
value: string;
};
type B = {
type: "B";
value: number;
};
type Message = A | B;
export function handle<M extends Message>(callbacks: {
[K in M["type"]]: (msg: Extract<M, {type: K}>["value"]) => unknown;
}) {
window.addEventListener("message", (event) => {
const msg = event.data as M;
callbacks[msg.type as keyof typeof callbacks](msg.value);
});
}🙁 Actual behavior
In TypeScript 5.0, this code works. In TypeScript 5.1, it complains about msg.value, with the error
Argument of type 'string | number' is not assignable to parameter of type 'Extract<M, { type: M["type"]; }>["value"]'.
Type 'string' is not assignable to type 'Extract<M, { type: M["type"]; }>["value"]'.
Type 'string' is not assignable to type 'never'.ts(2345)
🙂 Expected behavior
Code should work as it did in 5.0.4. If this is now intended behavior, I would like to know how to fix my code.
Metadata
Metadata
Assignees
Labels
Working as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Activity
jakebailey commentedon Jun 16, 2023
Bisects to #53098.
Anders Hejlsberg (@ahejlsberg)
Andarist commentedon Jun 17, 2023
As a workaround you can use this:
export function handle<M extends Message>(callbacks: { - [K in M["type"]]: (msg: Extract<M, { type: K }>["value"]) => unknown; + [K in M["type"]]: (msg: (M & { type: K })["value"]) => unknown; }) { window.addEventListener("message", (event) => { const msg = event.data as M; callbacks[msg.type as keyof typeof callbacks](msg.value); }); }ahejlsberg commentedon Jun 26, 2023
I think the new behavior is defensible. The old behavior was too permissive and would for example allow
even though one or the other must be wrong.
I think this is an instance of the pattern that was addressed in #47109. I'd suggest rewriting the example to
Andarist commentedon Jun 26, 2023
It kinda means that the workaround that I suggested here is broken, right? I mean, if this
Extract-based solution is considered to be flawed (I understand the argument behind it) then the intersection variant should really be treated the same way.typescript-bot commentedon Jun 28, 2023
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.