Home > OS >  Why does TypeScript recursive type alias fail with generics
Why does TypeScript recursive type alias fail with generics

Time:04-08

I've got two type definitions describing almost the same thing:

 // compilation fails with `Type alias 'StringOrRecordOfStrings' circularly references itself.ts(2456)`
type StringOrRecordOfStrings = string | string[] | Record<string, StringOrRecordOfStrings>;

 // compiles without problems
type StringOrRecordOfStrings = string | string[] | { [property: string]: StringOrRecordOfStrings };

Is anyone able to explain why the first type definition doesn't compile?

CodePudding user response:

There isn't a lot of clear documentation on this, but recursive type references for properties in objects, index signatures, and mapped types have been around for quite a while. The following works as early as TypeScript 3.3:

type Recursive = {p: Recursive} | {[p: string]: Recursive} | {[Key in 'a']: Recursive}

TypeScript 3.3 playground

This is why your second example type (with an index signature) checks.

TypeScript 3.7 extended the support for recursive references, as explained in this PR:

  • Instantiations of generic class and interface types (for example Array<Foo>).
  • Array types (for example Foo[]).
  • Tuple types (for example [string, Foo?]).

So now, also these three examples are valid:

type RecursiveCI = Promise<RecursiveCI>
type RecursiveT = [number, boolean, RecursiveT]
type RecursiveA = RecursiveA[]

The reason why Record<string, StringOrRecordOfStrings> is not allowed is that Record is a generic type, rather than a class or an interface.

I assume the sample is just test code, but you can get it to type check by using a helper interface like this:

type StringOrRecordOfStrings = string | string[] | Record<string, RecordInterface>

interface RecordInterface extends Record<string, StringOrRecordOfStrings> {}

TypeScript playground

  • Related