Typescript: Recursive type declaration is expanded and contains errors

Created on 3 Jan 2020  路  3Comments  路  Source: microsoft/TypeScript

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

Design Limitation

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.

All 3 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  路  3Comments

manekinekko picture manekinekko  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

jbondc picture jbondc  路  3Comments

seanzer picture seanzer  路  3Comments