Skip to content

Incorrect generic inference inside a discriminated union #45809

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

Open
hednowley opened this issue Sep 9, 2021 · 3 comments
Open

Incorrect generic inference inside a discriminated union #45809

hednowley opened this issue Sep 9, 2021 · 3 comments
Assignees
Labels
Bug A bug in TypeScript Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@hednowley
Copy link

hednowley commented Sep 9, 2021

Bug Report

πŸ”Ž Search Terms

discrimated union generic extra property

πŸ•— Version & Regression Information

  • This changed between versions 4.1.5 and 4.2.3

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type Loadable<TValue> =
    | {
          state: "loading"
      }
    | {
          state: "complete"
          value: TValue
      }


function mapLoadable<TValue>(loadable: Loadable<TValue>, map: (value: TValue) => void): void {
}

type AugmentedLoadable =
    | {
          state: "loading"
          value: number
      }
    | {
          state: "complete"
          value: string
      }

function mapAugmented(augmented: AugmentedLoadable) {
    mapLoadable(augmented, () => {}) // ❌ Errors with "Argument of type 'AugmentedLoadable' is not assignable to parameter of type 'Loadable<number>'"
    mapLoadable<string>(augmented, () => {}) // βœ… Works now that type is generic is specified
}

πŸ™ Actual behavior

TypeScript incorrectly infers that augmented should be assignable to Loadable<number> rather than a Loadable<string>, because we've added an unrelated value: number property to it. It can be nudged into working by writing out the generic type.

πŸ™‚ Expected behavior

TypeScript to infer the correct generic of string.

@andrewbranch
Copy link
Member

The inference depends on the union order of AugmentedLoadable 😞

@andrewbranch andrewbranch added the Bug A bug in TypeScript label Sep 10, 2021
@andrewbranch andrewbranch added this to the TypeScript 4.5.1 milestone Sep 10, 2021
@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Dec 15, 2021
@awerlogus
Copy link

The simplest repro:

declare const a: <E, T>(either: [false, E] | [true, T]) => void

declare const b: [false, string] | [true, number]

// now:      const a: <string, string>(either: [false, string] | [true, string]) => void
// expected: const a: <string, number>(either: [false, string] | [true, number]) => void
a(b)

@CraigMacomber
Copy link

I think I hit the same or a similar issue. My repro for it boiled down to:

Playground

type A<T> = {
    c: unknown;
} & (new () => T);

interface B<T> {
    a: T;
}

type Union1<T> = A<T> | B<T>;
type Union2<T> = B<T> | A<T>;

function X<T>(objectClass: Union1<T>): void {}
function X2<T>(objectClass: Union2<T>): void {}

class Inferable {
    public b!: unknown;
    public static a: number;
}

X(Inferable); // Errors
X2(Inferable); // Builds

My case fails (union is order dependent) all the way back to TS 3.3 (as far as the playground goes) through latest nightly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

6 participants