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

Discriminated union expands boolean to Foo<true> | Foo<false> instead of Foo<boolean> #46518

Closed
SystemParadox opened this issue Oct 25, 2021 · 2 comments

Comments

@SystemParadox
Copy link

Bug Report

🔎 Search Terms

Discriminated unions, generic unions, union expansion, boolean expands to true | false

🕗 Version & Regression Information

Tested on 4.4.2, nightly and 3.9.10.

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about generics, unions, union expansion

Possibly related: #22596, #22630, #30029.

⏯ Playground Link

Playground link with relevant code

💻 Code

export interface ObservableBase<T> {
    (): T; // Read the current value
    (newValue: T): void; // Write a new value
    value: T;
}

export type Observable<T> = T extends any[] ? ObservableArray<T> : ObservableBase<T>;

interface ObservableArray<T extends any[]> extends ObservableBase<T> {
    /** Array push. */
    push: (arg: T[number]) => void;
}

function makeObservable<T>(initial: T): Observable<T> {
    return {
        value: initial,
    } as Observable<T>;
}

const x = true;
const obs = makeObservable(x);

export function foo(z: boolean) {
    obs(z); // write a new value (should be `(boolean) => void`)
    obs(); // read the current value (should be `() => boolean`)
}

🙁 Actual behavior

No overload matches this call.
  Overload 1 of 3, '(newValue: false): true | void', gave the following error.
    Argument of type 'boolean' is not assignable to parameter of type 'false'.
  Overload 2 of 3, '(newValue: true): false | void', gave the following error.
    Argument of type 'boolean' is not assignable to parameter of type 'true'.

Note that obs has a type of ObservableBase<false> | ObservableBase<true>. This doesn't seem right to me. I made an observable that could accept values of true or false, not an observable that only accepts true or an observable that only accepts false.

I was sure I remember seeing something in the TypeScript documentation that allowed a bit more control over when union types are expanded for generics that would prevent this kind of thing, but I have searched high and low and cannot find anything.

If you change makeObservable to return an ObservableBase<T> instead of an Observable<T> then obs correctly gets a type of ObservableBase<boolean> and everything works. But the discriminated union seems to break everything.

🙂 Expected behavior

The above should not have any type errors.

@SystemParadox
Copy link
Author

Aha! Yes that's exactly what I was looking for! Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants