-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Conditional type distribution leads to undesirable behavior for booleans #22596
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
Comments
But Daniel, |
Like, you're suggesting that we need to start interpreting |
Prior to distributive unions there wasn't really a place where a flattened union and an unflattened union would be perceived to behave differently, were there? |
Hey, I get why it happens, but you clearly see why most users wouldn't want this, right? |
Yeah, but I'm struggling to come to terms with the implications, since without just treating boolean as not-a-union (which may have some undesired effects), I don't see how you'd distinguish the intent of type Foo<T> = T extends any ? T[] : never
type FooAndFalse<T> = Foo<T | false>
type Bar = FooAndFalse<string | number | true> from the intent of your example. |
@DanielRosenwasser means this but this is very hard work. type BT<T> = T extends boolean ? T[] : never
type BL<T> = T extends true | false ? T[] : never
type A = BT<boolean> // boolean[]
type B = BL<boolean> // true[] | false[] |
This is what I use as a work around: type Atomic<T> = {__v: T} & {__dontDistribute};
type Foo<T> = T extends Atomic<infer U> ? ([U] extends [any] ? U[] : never) : (T extends any ? T[] : never);
type Bar = Foo<string | number | Atomic<boolean>>; The main problem with this is that |
I think we have a conflict between "Conditional types should always distribute over unions" and "Primitives (where possible) should behave the same as a union of all their possible values". e.g.: type Arrayify<T> = T extends Array<any> ? T : T[];
// Error
const b1: Arrayify<boolean> = [true, false];
// OK
const s1: Arrayify<string> = ["a", "b"]; The fact that the unit type I also don't like the fact that a conditional type does not behave the same as if you wrote its in-place substitution: type Arrayify<T> = T extends Array<any> ? T : T[];
// Error
const u1: Arrayify<string | null> = ["a", null];
// OK
const u2: Arrayify<Array<string | null>> = ["a", null];
// OK
const u3: Array<string | null> = ["a", null]; |
I think so too. I think Conditional type shouldn't distribute normal types to literal types without matching literal types. Another point: type B<T> = T extends true ? T : never;
type N<T> = T extends 0 ? T : never;
type b = B<boolean>; // true
type n = N<number>; // never
|
Is this not already thrown out with narrowing? Is this a reasonable summary of options?
|
Given that The issue here really isn't with I don't think there's any way we could magically get this "right" because the definition of right is all in the eye of the beholder. Sometimes you want union types to distribute, sometimes you don't. The important thing is that we be consistent in when we distribute and that we allow you to opt out:
Here's one way to write the original example where you explicitly control which types are grouped: type ArrayOrNever<T> = [T] extends [never] ? never : T[];
type Foo<T> =
ArrayOrNever<Extract<T, string>> |
ArrayOrNever<Extract<T, number>> |
ArrayOrNever<Extract<T, boolean>> |
ArrayOrNever<Exclude<T, string | number | boolean>>;
type T0 = Foo<string | number | boolean>; // string[] | number[] | boolean[]
type T1 = Foo<'a' | 'b' | 0 | 1 | 2 | { a: string }>; // ('a' | 'b')[] | (0 | 1 | 2)[] | { a: string }[] Now, I'm still not sure what you'd actually do with this type. Regarding @RyanCavanaugh's example above, it seems to me that you'd never want arrayification to be distributive, so: type Arrayify<T> = [T] extends [Array<any>] ? T : T[]; |
Please see #22630 for the example. |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
Expected
Bar
has typestring[] | number[] | boolean[]
Actual
Bar
has typestring[] | number[] | true[] | false[]
The text was updated successfully, but these errors were encountered: