aliasing discriminated union tag breaks case analysis #10830

Closed
Swatinem opened this Issue Sep 9, 2016 · 5 comments

Projects

None yet

4 participants

@Swatinem
Swatinem commented Sep 9, 2016 edited

TypeScript Version: 2.1.0-dev.20160909

Code

type A = {t: "a", a: string};
type B = {t: "b", b: string};
type U = A | B;

const a: U = {t: "a", a: "a"} as U;

if (a.t === "b") {
    a.b; // works as expected
}

const {t} = a;
if (t === "b") {
    a.b; // error TS2339: Property 'b' does not exist on type 'U'.
}

Expected behavior:

The type of a.t should correctly transfer to t when assigning or using destructuring. t should be usable as discriminant.

Actual behavior:

The type of t is widened from "a" | "b" to string and therefore can not be used as discriminant any more.

As the same happens also for flow (See facebook/flow#2409), it may be my thinking that is a little bit off. But I think that the case should be valid. The programmer should not be forced to repeat base. all over, that’s what variables are for.

@RyanCavanaugh
Member

The type of a.t should correctly transfer to t when assigning or using destructuring. t should be usable as discriminant.

Tracking this correctly would require that variables "remember where they came from" and what implications that has on other variables. It'd probably be possible/reasonable to make that work at 1 level of indirection (rather than at 0 levels of indirection), but tracking it for N levels of indirection would be very complex, and then presumably you'd be surprised when it worked at 1 level of indirection but not 2.

So given the choice between "be surprised at 2 and things get way more complex in the compiler implementation" vs "be surprised at 1", we're generally sticking with the latter.

@mhegazy mhegazy added the Too Complex label Sep 9, 2016
@mhegazy mhegazy closed this Sep 9, 2016
@ligaz
ligaz commented Sep 15, 2016

What about the following case:

const { type, payload } = action;
  switch (type) {
    case 'SOME_STRING':

Is it too complex or there is possibility that you can add support for it?

@mhegazy
Contributor
mhegazy commented Sep 15, 2016

@ligaz this already works as intended:

declare const action: { type: "A", payload: number } | { type: "B", payload: string };


const { type, payload } = action;
switch (type) {
    case 'SOME_STRING': // "SOME_STRING"" is not compatible with "A" | "B"

}
@ligaz
ligaz commented Sep 16, 2016

The problem is that the payload is not typed if the tag is matched:

declare const action: { type: "A", payload: number } | { type: "B", payload: string };

const { type, payload } = action;
switch (type) {
    case 'A': // payload type is 'number | string' not 'number'.

}
@mhegazy
Contributor
mhegazy commented Sep 16, 2016

There is no way to know that there is a relationship between the two variables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment