Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.5 Breaks function assignments that use a complex Discriminated Unions Type #31833

Closed
alitaheri opened this issue Jun 9, 2019 · 5 comments
Closed
Labels
Bug A bug in TypeScript
Milestone

Comments

@alitaheri
Copy link

TypeScript Version: 3.4.5

Search Terms: Discriminated Unions Type, Function, Assignment, Type Property

Code

type Foo1 = {
  type: 'foo1';
  extra: number;
};

type Foo2 = {
  type: 'foo2';
  extra: string;
};

type Both = Foo1 | Foo2;

type FooTypes = Both['type'];

export type FooFromType<T extends FooTypes, O extends Both = Both> = O extends { type: T } ? O : never;

type FooExtraFromType<T extends FooTypes> = FooFromType<T>['extra'];

function fnWithFooExtra<T extends FooTypes>(type: T, extra: FooExtraFromType<T>) { }

// changing this to typeof fnWithFooExtra fixes it, even though it produces the exact signature
type FnType = <T extends FooTypes>(type: T, extra: FooExtraFromType<T>) => void;

// The error is produced here:
const fn: FnType = fnWithFooExtra;

Worked for a long time, but 3.5.1 breaks it. it hasn't been fixed on next. I tried 3.6.0-dev.20190608.

Interestingly, it breaks only on extra and not on the type itself. meaning that if I change FooExtraFromType to FooFromType it works.

If I use typeof instead of writing the signature it works. this is really weird as they are the exact same signature.

Expected behavior:

Compile as it had been working up to 3.4.5.

Actual behavior:

'fn' is declared but its value is never read.ts(6133)
Type '<T extends "foo1" | "foo2">(type: T, extra: (FooFromType<T, Foo1> | FooFromType<T, Foo2>)["extra"]) => void' is not assignable to type 'FnType'.
  Types of parameters 'extra' and 'extra' are incompatible.
    Type 'FooFromType<T, Foo1>["extra"] | FooFromType<T, Foo2>["extra"]' is not assignable to type 'FooFromType<T, Foo1>["extra"] & FooFromType<T, Foo2>["extra"]'.
      Type 'FooFromType<T, Foo1>["extra"]' is not assignable to type 'FooFromType<T, Foo1>["extra"] & FooFromType<T, Foo2>["extra"]'.
        Type 'number' is not assignable to type 'FooFromType<T, Foo1>["extra"] & FooFromType<T, Foo2>["extra"]'.
          Type 'number' is not assignable to type 'FooFromType<T, Foo2>["extra"]'.
            Type 'number' is not assignable to type 'string'.
              Type 'FooFromType<T, Foo1>["extra"]' is not assignable to type 'FooFromType<T, Foo2>["extra"]'.
                Type 'FooFromType<T, Foo1>' is not assignable to type 'FooFromType<T, Foo2>'.
                  Type 'FooFromType<T, Foo1>["extra"]' is not assignable to type 'string'.
                    Type 'number' is not assignable to type 'string'.ts(2322)

Playground Link: Doesn't break on playground, I guess the version is old.

@fatcerberus
Copy link

fatcerberus commented Jun 9, 2019

Working Playground link

Indexed access type + contravariance + inferred intersection = #30769, I'd wager.

@jack-williams
Copy link
Collaborator

Fix up: #31837

@jack-williams
Copy link
Collaborator

jack-williams commented Jun 9, 2019

Here is a smaller repro.

// You need the two aliases to avoid variance measurements.

type A1 = <
T extends { x: number, y: string } | { x: boolean, y: number}
>(
  x: T["x" | "y"]
) => void

type A2 = <
T extends { x: number, y: string } | { x: boolean, y: number}
>(
  x: T["x" | "y"]
) => void

declare const a: A1;
let b: A2 = a; // error, even though A1 and A2 are identical.

Another interesting case:

type Obj = { x: number, y: string } | { x: boolean, y: number};
function fun<T extends Obj>(l: { x: T["x" | "y"] }, r: { x: T["x" | "y"] }) {
    l = r; // was ok, now error
}

@jcalz
Copy link
Contributor

jcalz commented Feb 21, 2021

oof, just bonked into this with code like

interface A {
    k: "a";
    v: string
}

interface B {
    k: "b";
    v: number
}

interface I {
    method<K extends "a" | "b">(
        k: K,
        v: Extract<A | B, { k: K }>["v"]
    ): void;
}

class C implements I {
    method<K extends "a" | "b">( // error!
    //~~~~ Type 'Extract<A, { k: K; }>["v"]' is not assignable to 
    // type 'Extract<A, { k: K; }>["v"] & Extract<B, { k: K; }>["v"]'.
        k: K,
        v: Extract<A | B, { k: K }>["v"]
    ) { }
}

in this SO question

@RyanCavanaugh
Copy link
Member

No longer errors, though jcalz's still does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
5 participants