-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Open
Labels
In DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
Description
🔎 Search Terms
generic type variable intersection union order
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics
⏯ Playground Link
💻 Code
type Union<A, B> = A | B
type UnionAny<T> = Union<T, any>
type UnionUnknown<T> = Union<unknown, T>
// these should all be the same type but are respectively: any, any, unknown
type UA0 = Union<unknown, any>
// ^?
type UA1 = UnionAny<unknown>
// ^?
type UA2 = UnionUnknown<any>
// ^?
type Intersect<A, B> = A & B
type IntersectAny<T> = Intersect<T, any>
type IntersectNever<T> = Intersect<never, T>
// these should all be the same type but are respectively: never, any, never
type AN0 = Intersect<never, any>
// ^?
type AN1 = IntersectAny<never>
// ^?
type AN2 = IntersectNever<any>
// ^?
🙁 Actual behavior
The types UA0
, UA1
are resolved as any
and UA2
is resolved as unknown
. These all map down to the same union of unknown
and any
, so should all be the same type.
The types AN0
, AN2
are resolved as never
and AN1
resolved as any
. These all map down to be the intersection of any
and never
, so should all be the same type.
🙂 Expected behavior
I expect UA0
, UA1
and UA2
to resolve as the same type (probably any
, but preferably unknown
)
I expect AN0
, AN1
and AN2
to resolve as the same type (probably never
)
Additional information about the issue
No response
fatcerberus, whzx5byb and craigphicks
Metadata
Metadata
Assignees
Labels
In DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
[-]Intersection, Union depends on type variable resolution[/-][+]Intersection, Union depends on type variable instantiation order[/+]rotu commentedon Dec 30, 2023
The issue may be that
unknown
is assumed to be an absorbing element for|
andany
an absorbing element for&
. I think it's reasonable that a type expression containing type parameters should depend only on the eventual value of those type parameters.fatcerberus commentedon Dec 30, 2023
Generally speaking, both union and intersection type operators are commutative (with the exception of function intersections, which are treated as overloads and are order-dependent), and any union/intersection containing
any
should always reduce toany
.rotu commentedon Dec 31, 2023
@fatcerberus gotcha.
I think it would be logically consistent for either:
any | unknown
andany & never
are bothany
, sinceany
represents degeneracy of the type system.T | unknown
isunknown
,T & never
isnever
, sinceunknown
andnever
bound the type hierarchy andany
contains no information about the qualified value.Why should the former be overriding? I was unable to find docs that make this clear, but it feels like an important design choice.
Edit: I can see that the intent was for
unknown & any
andunknown | any
to both beany
in #24439.But the documented behavior there differs from current, namely:which seems more consistent than the current behavior (where this type evaluates asnever
)Edit again: my understanding current behavior for distributive conditional was wrong. It only distributes when the left-hand side is a type parameter.
fatcerberus commentedon Dec 31, 2023
Specifically, a naked type parameter. Stuff like
(T & U) extends ...
or whatever is also not distributive. Which is why you can write[T] extends [U] ? ...
to prevent distribution.rotu commentedon Dec 31, 2023
Oy. You’re right. And I can’t figure out when that’s a bug or a feature.
For instance
T|T
behaves like the naked type parameterT
butT|U
does not, even whenT
andU
are instantiated with the same type!There’s also the curious case that even when the LHS does not contain a type parameter, when the LHS is
any
but the RHS is not, the conditional evaluates to the union of both branches.fatcerberus commentedon Dec 31, 2023
Yeah, that’s a separate behavior from distribution and is intentional.
rotu commentedon Dec 31, 2023
Yes, it’s intentional. The reason for it (
any
is an upper bound ofunion
, so we should infer the most general type possible) seems to imply thatunknown extends T ? A : B
should evaluate toA | B
.