Typescript: Generic conditional type T extends never ? 'yes' : 'no' resolves to never when T is never.

Created on 4 Jun 2019  Â·  4Comments  Â·  Source: microsoft/TypeScript


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 still resolves to never.

Playground Link:
Here

Related Issues:
None

Working as Intended

Most helpful comment

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 

All 4 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

manekinekko picture manekinekko  Â·  3Comments

uber5001 picture uber5001  Â·  3Comments

blendsdk picture blendsdk  Â·  3Comments

dlaberge picture dlaberge  Â·  3Comments

Roam-Cooper picture Roam-Cooper  Â·  3Comments