Typescript: `DeepPartial` type breaks when using `any`

Created on 25 Feb 2019  路  3Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.3.3333


Search Terms: deep partial any DeepPartial

Code

type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

type User = { name: string; age: number; }
type Test = { user: User; query: any };
const test: DeepPartial<Test> = {
  user: {
    name: 'bob',
  },
  // Unexpected error: Type 'string' is not assignable to type 'DeepPartial<any> | undefined'.
  query: { foo: 'string' },
};

type Test2 = any;
const test2: DeepPartial<Test2> = {
    // Unexpected error: Type 'string' is not assignable to type 'DeepPartial<any> | undefined'
    foo: 'bar'
};
Working as Intended

Most helpful comment

You always need an exit condition for recursive definitions:

type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;

All 3 comments

You always need an exit condition for recursive definitions:

type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;

First, as @dawidgarus says above, it is definitely a good idea to have an explicit exit condition for recursive types.

Your example is working as intended. keyof any is string | number | symbol, so a mapped type applied to any yields the equivalent of { [x: string]: XXX }. In your case you therefore end up with an infinite expansion { [x: string]: DeepPartial<any> }.

You could argue that we should just yield any when a homomorphic mapped type is applied to any, but that would be less precise. For example { [K in keyof T]: number } instantiated with T as any really ought to yield { [x: string]: number } and not just any.

Thanks

Was this page helpful?
0 / 5 - 0 ratings