How to conditionally detect the `any` type in TypeScript?
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
ifT
isany
.never
ifT
isnever
.false
ifT
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
.
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
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:
- Check to see if type returns both sides of conditional as a union
- Merge the union members into an intersection, and see if the result is
never
.
// From: https://stackoverflow.com/a/50375286/62076
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