TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Mapped type with key remapping behaves differently when remapping gets inlined

Open tomquist opened this issue 2 years ago • 0 comments

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.

tomquist avatar Nov 28 '22 10:11 tomquist