4

Let's say we have the following type I:

type I = () => () => () => "a" | "b" | "c";

Is there a way to create a generic type Unwrap such that Unwrap<I> evaluates to "a" | "b" | "c"?

type I = () => () => () => "a" | "b" | "c";

type Result = Unwrap<I>; // "a" | "b" | "c"

The following (ofc) produces a circularity error:

type Unwrap<
  T extends (...args: any[]) => any,
  R = ReturnType<T>
> = R extends (...args: any[]) => any
  ? Unwrap<R>
  : R;

Any help would be greatly appreciated. Thank you!

Harry Solovay
  • 483
  • 3
  • 14
  • 3
    There may be a workaround you can use now, but the good news is that in TypeScript 4.1.0 (when it releases), your code will work as-is! [Here is the PR](https://github.com/microsoft/TypeScript/pull/40002) that adds support for recursive conditional types. You can even try it out if you use the "nightly" option on the [Typescript playground](https://www.typescriptlang.org/play?ts=4.1.0-dev.20200824#code/FAFwngDgpgBAkjAvDAFASiQPlRx31YwBEAhkTAD7EBG5VRAxkQNzCiSwCqAdgO4BOJCAB4AKtmSiYUAB4go3ACYBnVADoNJfgHNlALhgluYANoBdXNgCW3AGZR+MTjAD8TvoJGdsB0a3bQMABKUMoArgA2IEjuAkLCcJjMMAD0KcRklDR0xExswEA). – CRice Aug 24 '20 at 23:05
  • 2
    In my opinion the workarounds are so awful that I'd just start using `typescript@next` unless you have some absolute need otherwise. – jcalz Aug 24 '20 at 23:37

2 Answers2

2

Well here's the hack which works in TypeScript 3. It's actually not that awful.

type I = () => (() => (() => "a" | "b" | "c")) | "e" | (() => "f" | "g");

type Unwrap<T> =
    T extends (...args: any[]) => infer R
        ? { 0: Unwrap<R> }[T extends any ? 0 : never] // Hack to recurse.
    : T;

type Result = Unwrap<I>;
// type Result = "e" | "a" | "b" | "c" | "f" | "g";

Playground Link

Mingwei Samuel
  • 2,917
  • 1
  • 30
  • 40
1

As listed in the comment above, the Unwrap type works in TypeScript 4.1 (soon-to-be released).

Harry Solovay
  • 483
  • 3
  • 14