Skip to content

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

Closed

Description

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.

Activity

  1. fatcerberus commented on Jun 9, 2019

    @fatcerberus

    Working Playground link

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

  2. jack-williams commented on Jun 9, 2019

    @jack-williams
    Collaborator

    Fix up: #31837

  3. jack-williams commented on Jun 9, 2019

    @jack-williams
    Collaborator

    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
    }
  4. jcalz commented on Feb 21, 2021

    @jcalz
    Contributor

    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

  5. RyanCavanaugh commented on Mar 5, 2024

    @RyanCavanaugh
    Member

    No longer errors, though jcalz's still does.

  6. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions