How to conditionally detect the `any` type in TypeScript? [duplicate]
Asked Answered
L

3

13

I would like an utility that I can use as IsStrictlyAny<T> and it will resolve to the type true if T is exactly any and to the false type otherwise. How can I do this?

My first idea:

type IsStrictlyAny<T> = any extends T ? true : false;

Results:

  • IsStrictlyAny<any>: true (good!)
  • IsStrictlyAny<unknown>: true (bad! - I want false)
  • IsStrictlyAny<string>: boolean (bad! - I want false)
Loleta answered 5/5, 2020 at 23:23 Comment(0)
N
15

This is a good question, and at first I thought it was impossible, but after some investigating, I think there's a way.

First of all, check this out:

type Test = any extends never ? 'A' : 'B' // "A" | "B"

What that means is that typescript knows that any could be anything, and it therefore cannot decide which side of the conditional to return, so it returns both sides as a union. I'm reasonably certain that any is the only case that would behave this way.

So then you just need to try to detect if a union was returned or a single value. To do that, we use two tools.

First, note that the intersection of two incompatible types is never.

type Test = 'A' & 'B' // never

Which makes sense, since a value cannot be two different strings at the same time.

Second, if we can get an intersection of all the members of the type union, we can then test see if it's never, or it's any other valid type. This answer has a helper to convert a union to an intersection, so I wont bother explaining it.

So to some up:

  1. Check to see if type returns both sides of conditional as a union
  2. Merge the union members into an intersection, and see if the result is never.
// From: https://mcmap.net/q/76498/-transform-union-type-to-intersection-type
type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

// If T is `any` a union of both side of the condition is returned.
type UnionForAny<T> = T extends never ? 'A' : 'B'

// Returns true if type is any, or false for any other type.
type IsStrictlyAny<T> =
  UnionToIntersection<UnionForAny<T>> extends never ? true : false

type A = IsStrictlyAny<any>     // true
type B = IsStrictlyAny<string>  // false
type C = IsStrictlyAny<unknown> // false

Playground

Natala answered 6/5, 2020 at 0:24 Comment(2)
Wow impressive!Operant
Admittedly, this answer of mine is not the best answer!Natala
R
14

The simplest answer I've found is in the answer to the question this duplicates, and the explanation is in a related answer:

type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N; 
type IsAny<T> = IfAny<T, true, false>;

type A = IsAny<any>; // true
type B = IsAny<unknown>; // false
type C = IsAny<string>; // false
type D = IsAny<never>; // false

The short reason why this works is that 0 extends (1 & T) should be false for any type T that "plays by the rules". 0 isn't assignable to 1 so it really shouldn't be assignable to 1 & T no matter what T is. But any doesn't play by the rules: 1 & any evaluates to any, and 0 extends any is true.

Hope that helps; good luck!

Playground link to code

Ricarda answered 6/5, 2020 at 2:2 Comment(0)
S
5

The expression:

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

meets the criteria:

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

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

Playground.


This works because T extends never ? true : false when treated as a distributed conditional type resolves to:

  • boolean if T is any.
  • never if T is never.
  • false if T is anything else.

And then since both never and false are assignable to false, the final expression (...) extends false ? false : true is true only when T is any.

Skald answered 6/5, 2020 at 1:31 Comment(3)
Fascinating, I think this might actually be a bug in TypeScript? Maybe there is some sort of buggy short-circuiting of the nested conditional type check?Loleta
@PedroA I wondered the same, but I was pointed in the right direction by the excellent jcalz. This is actually a consequence of distributed conditional types, a feature of typescript which I did not fully understand (until now).Skald
Nice, I will look into it, thank you very much!!Loleta

© 2022 - 2024 — McMap. All rights reserved.