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://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