2

While experimenting for a type isStrictlyAny per the question here, I arrived at an expression:

type IsStrictlyAny<T> = (T extends never ? true : false) extends false ? false : true;

which when used then produces:

type t1 = IsStrictlyAny<any>;     // true
type t2 = IsStrictlyAny<unknown>; // false
type t3 = IsStrictlyAny<string>;  // false
type t4 = IsStrictlyAny<never>;   // false!

This makes sense mostly. But for the last case I noted that "manually" expanding it like so:

type t5 = (never extends never ? true : false) extends false ? false : true; // true!!!

is true, contradicting the above.

Why is this?


Link to playground.

CRice
  • 29,968
  • 4
  • 57
  • 70
  • 2
    Your "manual" expansion is no longer a [distributive conditional type](https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types). See [the answer to this question](https://stackoverflow.com/questions/55382306/typescript-distributive-conditional-types) for more information. – jcalz May 06 '20 at 01:54
  • @jcalz I am beginning to understand. But here, the type parameter is just `never`, which is not a union. There is nothing to distribute over, so I still don't see how the expanded version differs. In that linked question, I visualize it expanding as `("a" extends B ? never : "a") | ( "b" extends B ? never : "b" ) | ...` and I see why it works, but I cannot do the same here... – CRice May 06 '20 at 02:27
  • 1
    Get ready for your mind to be blown... `never` is treated as [*the empty union*](https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379094672). So given `F = T extends X ? Y : Z`, `F` will always be `never`. This is consistent since a distributive conditional type satisfies `F = F | F` and thus `F = F | F` meaning `F = F | F` for all distributive `F` and for all `A`, so that means `F` should be `never`. – jcalz May 06 '20 at 02:31
  • @jcalz You know, when I said there was "*nothing* to distribute over", this isn't what I had in mind... Thanks, this clicked for me, I see it now. – CRice May 06 '20 at 02:41

1 Answers1

2

This is a consequence of TypeScript's distributed conditional types, combined with the fact that never is treated as an empty union.

When using a naked conditional type, as you do here, TypeScript will distribute the condition onto each part of the type, and the result will be the union of each part after the condition is applied. Since never is just a union of 0 parts, the union of each part after the condition is applied is still just an empty union, aka never.

Put briefly, for any distributed conditional type DCT<T>, DCT<never> is never.

Thus, for the final case of your example, the result of T extends never ? true : false is neither true nor false, but never, which makes the final expression:

never extends false ? false : true

and since never extends false (and everything else), the result of this is false.

CRice
  • 29,968
  • 4
  • 57
  • 70