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?
- The recursive type aliases have been introduced in version 3.7 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#more-recursive-type-aliases
- This SO answer explains how to use recursive type aliases Recursive Types in TypeScript
- for brevity I omitted the remaining key types from record, but it works even when we set the
[property: string | number | symbol]
.
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}
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> {}