TypeScript Version: 3.7.2
Search Terms:
recursive type declaration
Expected behavior:
Declaration contains more or less the same type definition as in the source
Actual behavior:
Declaration contains expanded recursion down to 10 levels and has errors
Related Issues:
Code
export type TypeMap = {
"number?"?: number;
number: number;
"string?"?: string;
string: string;
"boolean?": boolean;
boolean: boolean;
};
export type OptionsArg = {
[key: string]: [keyof TypeMap, string] | [keyof TypeMap] | OptionsArg;
};
export function options<T extends OptionsArg>(opts: T) {
type Options<O extends OptionsArg> = {
[key in keyof O]: O[key][0] extends keyof TypeMap
? TypeMap[O[key][0]]
: O[key] extends OptionsArg
? Options<O[key]>
: never;
};
return {
type: {} as Options<T>
};
}
const { type } = options({
optionalFoo: ["number?"],
nested: {
bar: ["string"]
}
});
type.optionalFoo; // number | undefined
type.nested.bar; // string
Declaration Output
export interface TypeMap {
"number?"?: number;
number: number;
"string?"?: string;
string: string;
"boolean?": boolean;
boolean: boolean;
}
export interface OptionsArg {
[key: string]: [keyof TypeMap, string] | [keyof TypeMap] | OptionsArg;
}
export declare function options<T extends OptionsArg>(opts: T): {
type: { [key in keyof T]: T[key][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][0]] : T[key] extends OptionsArg ? { [key_1 in keyof T[key]]: T[key][key_1][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][0]] : T[key][key_1] extends OptionsArg ? { [key_2 in keyof T[key][key_1]]: T[key][key_1][key_2][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][0]] : T[key][key_1][key_2] extends OptionsArg ? { [key_3 in keyof T[key][key_1][key_2]]: T[key][key_1][key_2][key_3][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][0]] : T[key][key_1][key_2][key_3] extends OptionsArg ? { [key_4 in keyof T[key][key_1][key_2][key_3]]: T[key][key_1][key_2][key_3][key_4][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][0]] : T[key][key_1][key_2][key_3][key_4] extends OptionsArg ? { [key_5 in keyof T[key][key_1][key_2][key_3][key_4]]: T[key][key_1][key_2][key_3][key_4][key_5][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][key_5][0]] : T[key][key_1][key_2][key_3][key_4][key_5] extends OptionsArg ? { [key_6 in keyof T[key][key_1][key_2][key_3][key_4][key_5]]: T[key][key_1][key_2][key_3][key_4][key_5][key_6][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][key_5][key_6][0]] : T[key][key_1][key_2][key_3][key_4][key_5][key_6] extends OptionsArg ? { [key_7 in keyof T[key][key_1][key_2][key_3][key_4][key_5][key_6]]: T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][0]] : T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7] extends OptionsArg ? { [key_8 in keyof T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7]]: T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][0]] : T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8] extends OptionsArg ? { [key_9 in keyof T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8]]: T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9][0]] : T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9] extends OptionsArg ? { [key_10 in keyof T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9]]: T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9][key_10][0] extends "string" | "number" | "boolean" | "number?" | "string?" | "boolean?" ? TypeMap[T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9][key_10][0]] : T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9][key_10] extends OptionsArg ? any : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; } : never; };
};
/*
ERRORS:
Type '0' cannot be used to index type 'T[key][key_1][key_2]'.
Type '0' cannot be used to index type 'T[key][key_1][key_2][key_3][key_4]'.
Type '0' cannot be used to index type 'T[key][key_1][key_2][key_3][key_4][key_5][key_6]'.
Type '0' cannot be used to index type 'T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8]'.
Type '0' cannot be used to index type 'T[key][key_1][key_2][key_3][key_4][key_5][key_6][key_7][key_8][key_9][key_10]'.
*/
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"declaration": true,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}
Playground Link: Provided
You're not using a type alias that is "visible" from the outside. TS cannot use that type alias in declaration emit. So, it has no choice but to expand it.
Always add explicit return type annotations to generic functions. Especially if the return type is complicated.
Move that type declaration outside the function, too.
@AnyhowStep thanks, you're right! Though maybe it's still can be considered a bug that it expands the type with errors, probably there should be some kind of warning that the user is not exposing recursive type
We try to do our best here, but there are certain constraints about what the declaration emitter can do in terms of reasoning about whether or not an emitted anonymous type will end up being "equivalent enough" to the unreferenceable type needed
Most helpful comment
You're not using a type alias that is "visible" from the outside. TS cannot use that type alias in declaration emit. So, it has no choice but to expand it.
Always add explicit return type annotations to generic functions. Especially if the return type is complicated.
Move that type declaration outside the function, too.