Skip to content
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

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

Closed
rickwebiii opened this issue Jun 4, 2019 · 8 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@rickwebiii
Copy link

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

@dragomirtitian
Copy link
Contributor

dragomirtitian commented Jun 4, 2019

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 

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 4, 2019
@rickwebiii
Copy link
Author

Thanks for the clarification. I should really get around to reading my Homotopy Type Theory book.

@fatcerberus
Copy link

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. 😉

@jack-williams
Copy link
Collaborator

@fatcerberus; Discussed in #27418

@Alexsey
Copy link

Alexsey commented Jul 27, 2021

I've got here because I was unable to understand why MakesSense and Huh are getting evaluated differently i.e. why both of them are not never. So probably here is a good place to post the answer:
According to the handbook chapter on Distributed Conditional Types

When conditional types act on a generic type, they become distributive when given a union type

It means that when Huh is evaluated, the never value of T is getting distributed and things are happening as @dragomirtitian explained. But evaluation of MakesSense doesn't include generics, so Conditional Type doesn't get distributed and so it acts as "expected" - never is getting compared with never

@ZhanghaoH
Copy link

never is the empty union (ie a union with no members)

but type-challenges-01097 ,its last test example thinks Never is not Union Type

@Alexsey
Copy link

Alexsey commented Jul 19, 2022

@ZhanghaoH you can think of the type challenge problem not as "isUnion" but as "isDistributiveUnion" or "isUnionOfMoreThenOneElement". 1 is also a union - a union of 1 element. And never is a union - an empty union, or a union of 0 elements

Unions of 0 elements, 1 element, and 1+ elements behave differently in TS, and the problem is about detecting the third, non-singular case

@ZhanghaoH
Copy link

I see...thanks for your reply !

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

7 participants