Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)
like image 907
Pedro A Avatar asked May 05 '20 23:05

Pedro A


People also ask

What is conditional type in typescript?

Conditional Types in TypeScript. TypeScript 2.8 introduced conditional types, a powerful and exciting addition to the type system. Conditional types let us express non-uniform type mappings, that is, type transformations that differ depending on a condition.

What is any type in typescript?

The any type is a powerful way to work with existing JavaScript, allowing you to gradually opt-in and opt-out of type checking during compilation. Let that sink in. The TypeScript documentation express clearly that when we have the any type, we are telling the compiler:

Is Ruby’s “object” the same as typescript object?

Most of the uses we have seen indicate that we are dealing with the base type in TypeScript land. Ruby’s is Object same for C#, inside the documentation we find: (…) values from code that has been written without TypeScript or a 3rd party library. In these cases, we might want to opt-out of type checking.

When do conditional types become distributive in JavaScript?

When conditional types act on a generic type, they become distributive when given a union type. For example, take the following: type ToArray < Type > = Type extends any ? Type [] : never; If we plug a union type into ToArray, then the conditional type will be applied to each member of that union. type ToArray < Type > = Type extends any ?


2 Answers

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

like image 183
Alex Wayne Avatar answered Oct 19 '22 10:10

Alex Wayne


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

like image 12
jcalz Avatar answered Oct 19 '22 09:10

jcalz