TypeScript Version: 3.6.0-dev.20190603
Search Terms:
never, extends, generic, conditional type
Code
type MakesSense = never extends never ? 'yes' : 'no' // Resolves to 'yes'
type ExtendsNever<T> = T extends never ? 'yes' : 'no'
type MakesSenseToo = ExtendsNever<{}> // Resolves to 'no'
type Huh = ExtendsNever<never> // Expect to resolve to 'yes', actually resolves to never
Expected behavior:
Huh should resolve to 'yes', as MakesSense does.
Actual behavior:
Huh resolves to never.
For another variant, you can change ExtendsNever to
type ExtendsNever<T extends never> = T extends never ? 'yes' : 'no'
and ExtendsNever
Playground Link:
Here
Related Issues:
None
This is the expected behavior, ExtendsNever is a distributive conditional type. Conditional types distribute over unions. Basically if T is a union ExtendsNever is applied to each member of the union and the result is the union of all applications (ExtendsNever<'a' | 'b'> == ExtendsNever<'a' > | ExtendsNever<'b'>). never is the empty union (ie a union with no members). This is hinted at by its behavior in a union 'a' | never == 'a'. So when distributing over never, ExtendsNever is never applied, since there are no members in this union and thus the result is never.
If you disable distribution for ExtendsNever, you get the behavior you expect:
type MakesSense = never extends never ? 'yes' : 'no' // Resolves to 'yes'
type ExtendsNever<T> = [T] extends [never] ? 'yes' : 'no'
type MakesSenseToo = ExtendsNever<{}> // Resolves to 'no'
type Huh = ExtendsNever<never> // is yes
Thanks for the clarification. I should really get around to reading my Homotopy Type Theory book.
Why is never treated as an empty union but unknown isn’t treated as a “union of everything”? For symmetry I would expect T extends any ? ... where T = unknown to take both branches of the conditional type, the same way T = never takes neither.
I understand that any works this way but any isn’t really a “union of all types” in the usual sense - it also behaves as an “intersection of all types” when placed in a contravariant position so it’s kind of its own thing entirely and I expect it to act weird. :wink:
@fatcerberus; Discussed in #27418
Most helpful comment
This is the expected behavior,
ExtendsNeveris a distributive conditional type. Conditional types distribute over unions. Basically ifTis a unionExtendsNeveris applied to each member of the union and the result is the union of all applications (ExtendsNever<'a' | 'b'> == ExtendsNever<'a' > | ExtendsNever<'b'>).neveris the empty union (ie a union with no members). This is hinted at by its behavior in a union'a' | never == 'a'. So when distributing overnever,ExtendsNeveris never applied, since there are no members in this union and thus the result is never.If you disable distribution for
ExtendsNever, you get the behavior you expect: