TypeScript
TypeScript copied to clipboard
Mapped type with key remapping behaves differently when remapping gets inlined
Bug Report
🔎 Search Terms
Recursive Mapped Type, Key Remapping
🕗 Version & Regression Information
- This happens in all versions starting 4.1 (including nightly)
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about mapped types.
- I was unable to test this on versions before 4.1 because it uses Key Remapping introduced in 4.1
⏯ Playground Link
Playground link with relevant code
💻 Code
interface Identifiable {
id: string;
}
type IdentifiableOrNever<T, K> = T extends Identifiable ? K : never;
type RelationshipDefinition1<T> = T extends Identifiable
? { [K in keyof T as IdentifiableOrNever<T[K], K>]?: RelationshipDefinition1<T[K]> } & { $name: string }
: never;
// This type is identical to RelationshipDefinition1 but with inlined IdentifiableOrNever
type RelationshipDefinition2<T> = T extends Identifiable
? { [K in keyof T as T[K] extends Identifiable ? K : never]?: RelationshipDefinition2<T[K]> } & { $name: string }
: never;
export interface ChildModel {
id: string;
}
export interface RootModel {
id: string;
child?: ChildModel;
}
const definition1: RelationshipDefinition1<RootModel> = {
$name: "",
child: { $name: "" },
}
const definition2: RelationshipDefinition2<RootModel> = {
$name: "",
child: { $name: "" }, // <-- Type '{ $name: string; child: { $name: string; }; }' is not assignable to type '{} & { $name: string; }'. Object literal may only specify known properties, and 'child' does not exist in type '{} & { $name: string; }'.
}
🙁 Actual behavior
The variable declaration of definition2
doesn't compile with the following error, even though it uses an identical type than definition1
:
Type '{ $name: string; child: { $name: string; }; }' is not assignable to type '{} & { $name: string; }'.
Object literal may only specify known properties, and 'child' does not exist in type '{} & { $name: string; }'.
🙂 Expected behavior
RelationshipDefinition1<RootModel>
and RelationshipDefinition2<RootModel>
should be exactly the same type.