From b0a1df3b299bfe93ed305ca117b0b865ae99e26f Mon Sep 17 00:00:00 2001 From: bgenia Date: Sat, 18 Jan 2025 06:28:14 +0300 Subject: [PATCH 1/4] Allow getLiteralTypeFromProperty to extract name types from unions --- src/compiler/checker.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5eb14081e2a3c..ee3f0b21d6081 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18330,6 +18330,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); } + // Property name can only have a union type if it refers to multiple structurally identical declarations. (See addMemberForKeyTypeWorker) + // In this case, it's safe to take any of the constituents to compute the literal type. + if (type && type.flags & TypeFlags.Union) { + const unionType = type as UnionType; + if (unionType.types.length > 0) { + type = unionType.types[0]; + } + } if (type && type.flags & include) { return type; } From 12b9bd4621b6ea3d88fd4583a03aad1989346a9d Mon Sep 17 00:00:00 2001 From: bgenia Date: Sat, 18 Jan 2025 06:54:12 +0300 Subject: [PATCH 2/4] Add tests --- ...hUnionPropertyNamesShouldProduceMembers.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts diff --git a/tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts b/tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts new file mode 100644 index 0000000000000..e2baec5ad9137 --- /dev/null +++ b/tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts @@ -0,0 +1,26 @@ +/// + +//// enum Key { +//// A = "a", +//// B = "b" +//// } +//// +//// // Produces members with union name types (Key.A | "a", Key.B | "b") +//// type [|Foo|] = { [K in Key | `${Key}`]: 1 } +//// +//// // Should produce the same type as Foo +//// type [|Bar|] = { [K in keyof Foo]: 1 } + +const [Foo, Bar] = test.ranges() + +verify.quickInfoAt(Foo, +`type Foo = { + a: 1; + b: 1; +}`) + +verify.quickInfoAt(Bar, +`type Bar = { + a: 1; + b: 1; +}`) From 78b62ed0fc728adb3eb5e0ad7a19cd27e703386c Mon Sep 17 00:00:00 2001 From: bgenia Date: Thu, 23 Jan 2025 05:31:14 +0300 Subject: [PATCH 3/4] Add test case from the issue --- ...peOverMappedTypeWithOverlappingEnumKeys.js | 44 ++++++++++++ ...rMappedTypeWithOverlappingEnumKeys.symbols | 50 ++++++++++++++ ...verMappedTypeWithOverlappingEnumKeys.types | 68 +++++++++++++++++++ ...peOverMappedTypeWithOverlappingEnumKeys.ts | 21 ++++++ 4 files changed, 183 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.js create mode 100644 tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.symbols create mode 100644 tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.types create mode 100644 tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts diff --git a/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.js b/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.js new file mode 100644 index 0000000000000..3e7728ccb2cb7 --- /dev/null +++ b/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts] //// + +//// [mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts] +// https://github.com/microsoft/TypeScript/issues/41700 + +enum EnumA { + A = 'A', + B = 'B', +} + +// A second enum with at least one key also in EnumA +enum EnumB { + B = 'B', + C = 'C', +} + +type Mapped = { + [k in EnumA|EnumB]: string; +}; + +// Should work +const partial: Partial = { + [EnumA.B]: 'value', +}; + + +//// [mappedTypeOverMappedTypeWithOverlappingEnumKeys.js] +// https://github.com/microsoft/TypeScript/issues/41700 +var _a; +var EnumA; +(function (EnumA) { + EnumA["A"] = "A"; + EnumA["B"] = "B"; +})(EnumA || (EnumA = {})); +// A second enum with at least one key also in EnumA +var EnumB; +(function (EnumB) { + EnumB["B"] = "B"; + EnumB["C"] = "C"; +})(EnumB || (EnumB = {})); +// Should work +var partial = (_a = {}, + _a[EnumA.B] = 'value', + _a); diff --git a/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.symbols b/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.symbols new file mode 100644 index 0000000000000..0394b9cf4a8e5 --- /dev/null +++ b/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.symbols @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts] //// + +=== mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts === +// https://github.com/microsoft/TypeScript/issues/41700 + +enum EnumA { +>EnumA : Symbol(EnumA, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 0, 0)) + + A = 'A', +>A : Symbol(EnumA.A, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 2, 12)) + + B = 'B', +>B : Symbol(EnumA.B, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 3, 12)) +} + +// A second enum with at least one key also in EnumA +enum EnumB { +>EnumB : Symbol(EnumB, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 5, 1)) + + B = 'B', +>B : Symbol(EnumB.B, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 8, 12)) + + C = 'C', +>C : Symbol(EnumB.C, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 9, 12)) +} + +type Mapped = { +>Mapped : Symbol(Mapped, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 11, 1)) + + [k in EnumA|EnumB]: string; +>k : Symbol(k, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 14, 5)) +>EnumA : Symbol(EnumA, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 0, 0)) +>EnumB : Symbol(EnumB, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 5, 1)) + +}; + +// Should work +const partial: Partial = { +>partial : Symbol(partial, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 18, 5)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Mapped : Symbol(Mapped, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 11, 1)) + + [EnumA.B]: 'value', +>[EnumA.B] : Symbol([EnumA.B], Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 18, 34)) +>EnumA.B : Symbol(EnumA.B, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 3, 12)) +>EnumA : Symbol(EnumA, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 0, 0)) +>B : Symbol(EnumA.B, Decl(mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts, 3, 12)) + +}; + diff --git a/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.types b/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.types new file mode 100644 index 0000000000000..ccfb9c01c9a80 --- /dev/null +++ b/tests/baselines/reference/mappedTypeOverMappedTypeWithOverlappingEnumKeys.types @@ -0,0 +1,68 @@ +//// [tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts] //// + +=== mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts === +// https://github.com/microsoft/TypeScript/issues/41700 + +enum EnumA { +>EnumA : EnumA +> : ^^^^^ + + A = 'A', +>A : EnumA.A +> : ^^^^^^^ +>'A' : "A" +> : ^^^ + + B = 'B', +>B : EnumA.B +> : ^^^^^^^ +>'B' : "B" +> : ^^^ +} + +// A second enum with at least one key also in EnumA +enum EnumB { +>EnumB : EnumB +> : ^^^^^ + + B = 'B', +>B : EnumB.B +> : ^^^^^^^ +>'B' : "B" +> : ^^^ + + C = 'C', +>C : EnumB.C +> : ^^^^^^^ +>'C' : "C" +> : ^^^ +} + +type Mapped = { +>Mapped : Mapped +> : ^^^^^^ + + [k in EnumA|EnumB]: string; +}; + +// Should work +const partial: Partial = { +>partial : Partial +> : ^^^^^^^^^^^^^^^ +>{ [EnumA.B]: 'value',} : { B: string; } +> : ^^^^^^^^^^^^^^ + + [EnumA.B]: 'value', +>[EnumA.B] : string +> : ^^^^^^ +>EnumA.B : EnumA.B +> : ^^^^^^^ +>EnumA : typeof EnumA +> : ^^^^^^^^^^^^ +>B : EnumA.B +> : ^^^^^^^ +>'value' : "value" +> : ^^^^^^^ + +}; + diff --git a/tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts b/tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts new file mode 100644 index 0000000000000..952d4bc31b31e --- /dev/null +++ b/tests/cases/compiler/mappedTypeOverMappedTypeWithOverlappingEnumKeys.ts @@ -0,0 +1,21 @@ +// https://github.com/microsoft/TypeScript/issues/41700 + +enum EnumA { + A = 'A', + B = 'B', +} + +// A second enum with at least one key also in EnumA +enum EnumB { + B = 'B', + C = 'C', +} + +type Mapped = { + [k in EnumA|EnumB]: string; +}; + +// Should work +const partial: Partial = { + [EnumA.B]: 'value', +}; From b9a732bf6e06166745b3f5c475140ba767651fcb Mon Sep 17 00:00:00 2001 From: bgenia Date: Thu, 23 Jan 2025 07:43:11 +0300 Subject: [PATCH 4/4] Revert "Add tests" This reverts commit 12b9bd4621b6ea3d88fd4583a03aad1989346a9d. --- ...hUnionPropertyNamesShouldProduceMembers.ts | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts diff --git a/tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts b/tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts deleted file mode 100644 index e2baec5ad9137..0000000000000 --- a/tests/cases/fourslash/mappedTypesWithKeyofConstraintsOverObjectsWithUnionPropertyNamesShouldProduceMembers.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// - -//// enum Key { -//// A = "a", -//// B = "b" -//// } -//// -//// // Produces members with union name types (Key.A | "a", Key.B | "b") -//// type [|Foo|] = { [K in Key | `${Key}`]: 1 } -//// -//// // Should produce the same type as Foo -//// type [|Bar|] = { [K in keyof Foo]: 1 } - -const [Foo, Bar] = test.ranges() - -verify.quickInfoAt(Foo, -`type Foo = { - a: 1; - b: 1; -}`) - -verify.quickInfoAt(Bar, -`type Bar = { - a: 1; - b: 1; -}`)