-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Avoid distribution of conditional types over union types #29368
Comments
So.... I just found out that I actually can circumvent the distribution if I change RouteWithParamsMaker: type RouteWithParamsMaker <T extends Route> = (keyof T['Params']) extends infer U
? RouteWithParams<Extract<U, string>>
: never; This came a bit as a surprise - my initial feeling was that it should behave equally. So I guess one way to avoid the distribution is to extract conditionals in a clever way to new types. I would really like to have better guidance as a programmer here. |
To avoid distribution: wrap any naked type parameters in a 1-tuple, and do the same for the check type. type RouteWithParamsMaker<T extends Route> = (keyof T['Params']) extends infer U
? [U] extends [string]
? RouteWithParams<U>
: never
: never; I don't believe this is in the TypeScript handbook, but it probably should be. Tag for searching purpose: distributive conditional type trouble |
Conditional types are distributed across all types of a union. This is not what we want in the case of Config. Distribution happens only over "naked" type parameters and so wrapping the type in a 1-tuple as well as the check type avoids this behavior. See: microsoft/TypeScript#29368 (comment) Before this change, the type of leaf nodes would resolve to: { Bluetooth: { Allowed: ( Gettable<'False'> & Settable<'False'> & Listenable<'False'> ) | ( Gettable<'True'> & Settable<'True'> & Listenable<'True'> ) } } whereas what we want is: { Bluetooth: { Allowed: ( Gettable<'False' | 'True'> & Settable<'False' | 'True'> & Listenable<'False' | 'True'> ) } }
Conditional types are distributed across all types of a union. This is not what we want in the case of Config. Distribution happens only over "naked" type parameters and so wrapping the type in a 1-tuple as well as the check type avoids this behavior. See: microsoft/TypeScript#29368 (comment) Before this change, the type of leaf nodes would resolve to: { Bluetooth: { Allowed: ( Gettable<'False'> & Settable<'False'> & Listenable<'False'> ) | ( Gettable<'True'> & Settable<'True'> & Listenable<'True'> ) } } whereas what we want is: { Bluetooth: { Allowed: ( Gettable<'False' | 'True'> & Settable<'False' | 'True'> & Listenable<'False' | 'True'> ) } }
Conditional types are distributed across all types of a union. This is not what we want in the case of Config. Distribution happens only over "naked" type parameters and so wrapping the type in a 1-tuple as well as the check type avoids this behavior. See: microsoft/TypeScript#29368 (comment) Before this change, the type of leaf nodes would resolve to: { Bluetooth: { Allowed: ( Gettable<'False'> & Settable<'False'> & Listenable<'False'> ) | ( Gettable<'True'> & Settable<'True'> & Listenable<'True'> ) } } whereas what we want is: { Bluetooth: { Allowed: ( Gettable<'False' | 'True'> & Settable<'False' | 'True'> & Listenable<'False' | 'True'> ) } }
Conditional types are distributed across all types of a union. This is not what we want in the case of Config. Distribution happens only over "naked" type parameters and so wrapping the type in a 1-tuple as well as the check type avoids this behavior. See: microsoft/TypeScript#29368 (comment) Before this change, the type of leaf nodes would resolve to: { Bluetooth: { Allowed: ( Gettable<'False'> & Settable<'False'> & Listenable<'False'> ) | ( Gettable<'True'> & Settable<'True'> & Listenable<'True'> ) } } whereas what we want is: { Bluetooth: { Allowed: ( Gettable<'False' | 'True'> & Settable<'False' | 'True'> & Listenable<'False' | 'True'> ) } }
Conditional types are distributed across all types of a union. This is not what we want in the case of Config. Distribution happens only over "naked" type parameters and so wrapping the type in a 1-tuple as well as the check type avoids this behavior. See: microsoft/TypeScript#29368 (comment) Before this change, the type of leaf nodes would resolve to: { Bluetooth: { Allowed: ( Gettable<'False'> & Settable<'False'> & Listenable<'False'> ) | ( Gettable<'True'> & Settable<'True'> & Listenable<'True'> ) } } whereas what we want is: { Bluetooth: { Allowed: ( Gettable<'False' | 'True'> & Settable<'False' | 'True'> & Listenable<'False' | 'True'> ) } }
If you don't have a type to test against, This may be useful for others. type NoDistribute<T> = [T] extends [T] ? T : never; However, if T was used in a previous conditional type it may have already been distributed. If you have a deeply nested type you can use the single-element-tuple trick at each level around types you don't want distributed. |
How to handle type NotUndefinedAndNotVoid<T> = [T] extends [undefined]
? never
: [T] extends [void]
? never
: T | Promise<T>
type A = NotUndefinedAndNotVoid<any> // A is never since "[any] extends [undefined]" evaluates to true
type NotUndefinedAndNotVoidDistributed<T> = T extends undefined
? never
: T extends void
? never
: T | Promise<T>
type B = NotUndefinedAndNotVoidDistributed<any> // B is any because "any extends undefined" evaluates to false |
Search Terms
Suggestion
I would like to disable the distribution of union types over conditional types in certain cases. If this is already possible (not sure about that) it should be documented in a better way.
Use Cases
Suppose you would like to infer type parameters like so:
The resulting variable is currently inferred to have the type
Instead, I would like to receive the following type
I have no idea whether the current behaviour is intentional (and we would need a new annotation for such cases) or a bug.
Checklist
My suggestion meets these guidelines (not sure about the first and the last one):
The text was updated successfully, but these errors were encountered: