diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ed6768cb3d72..f2a34ae9a1a8e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6444,7 +6444,8 @@ namespace ts { for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { for (const member of (declaration).members) { - const memberType = getFreshTypeOfLiteralType(getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member))); // TODO: GH#18217 + const value = getEnumMemberValue(member); + const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } @@ -7453,8 +7454,9 @@ namespace ts { else if (t.flags & (TypeFlags.Any | TypeFlags.String)) { stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); } - else if (t.flags & TypeFlags.Number) { - numberIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); + else if (t.flags & (TypeFlags.Number | TypeFlags.Enum)) { + numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType, + !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); } } } @@ -28440,9 +28442,9 @@ namespace ts { if (member.initializer) { return computeConstantValue(member); } - // In ambient enum declarations that specify no const modifier, enum member declarations that omit - // a value are considered computed members (as opposed to having auto-incremented values). - if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent)) { + // In ambient non-const numeric enum declarations, enum members without initializers are + // considered computed members (as opposed to having auto-incremented values). + if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { return undefined; } // If the member declaration specifies no value, the member is considered a constant enum member. diff --git a/tests/baselines/reference/numericEnumMappedType.js b/tests/baselines/reference/numericEnumMappedType.js new file mode 100644 index 0000000000000..78a13e443ff42 --- /dev/null +++ b/tests/baselines/reference/numericEnumMappedType.js @@ -0,0 +1,110 @@ +//// [numericEnumMappedType.ts] +// Repro from #31771 + +enum E1 { ONE, TWO, THREE } +declare enum E2 { ONE, TWO, THREE } + +type Bins1 = { [k in E1]?: string; } +type Bins2 = { [k in E2]?: string; } + +const b1: Bins1 = {}; +const b2: Bins2 = {}; + +const e1: E1 = E1.ONE; +const e2: E2 = E2.ONE; + +b1[1] = "a"; +b1[e1] = "b"; + +b2[1] = "a"; +b2[e2] = "b"; + +// Multiple numeric enum types accrue to the same numeric index signature in a mapped type + +declare function val(): number; + +enum N1 { A = val(), B = val() } +enum N2 { C = val(), D = val() } + +type T1 = { [K in N1 | N2]: K }; + +// Enum types with string valued members are always literal enum types and therefore +// ONE and TWO below are not computed members but rather just numerically valued members +// with auto-incremented values. + +declare enum E { ONE, TWO, THREE = 'x' } +const e: E = E.ONE; +const x: E.ONE = e; + + +//// [numericEnumMappedType.js] +"use strict"; +// Repro from #31771 +var E1; +(function (E1) { + E1[E1["ONE"] = 0] = "ONE"; + E1[E1["TWO"] = 1] = "TWO"; + E1[E1["THREE"] = 2] = "THREE"; +})(E1 || (E1 = {})); +var b1 = {}; +var b2 = {}; +var e1 = E1.ONE; +var e2 = E2.ONE; +b1[1] = "a"; +b1[e1] = "b"; +b2[1] = "a"; +b2[e2] = "b"; +var N1; +(function (N1) { + N1[N1["A"] = val()] = "A"; + N1[N1["B"] = val()] = "B"; +})(N1 || (N1 = {})); +var N2; +(function (N2) { + N2[N2["C"] = val()] = "C"; + N2[N2["D"] = val()] = "D"; +})(N2 || (N2 = {})); +var e = E.ONE; +var x = e; + + +//// [numericEnumMappedType.d.ts] +declare enum E1 { + ONE = 0, + TWO = 1, + THREE = 2 +} +declare enum E2 { + ONE, + TWO, + THREE +} +declare type Bins1 = { + [k in E1]?: string; +}; +declare type Bins2 = { + [k in E2]?: string; +}; +declare const b1: Bins1; +declare const b2: Bins2; +declare const e1: E1; +declare const e2: E2; +declare function val(): number; +declare enum N1 { + A, + B +} +declare enum N2 { + C, + D +} +declare type T1 = { + [K in N1 | N2]: K; +}; +declare enum E { + ONE = 0, + TWO = 1, + THREE = "x" +} +declare const e: E; +declare const x: E.ONE; diff --git a/tests/baselines/reference/numericEnumMappedType.symbols b/tests/baselines/reference/numericEnumMappedType.symbols new file mode 100644 index 0000000000000..08979b3bed100 --- /dev/null +++ b/tests/baselines/reference/numericEnumMappedType.symbols @@ -0,0 +1,111 @@ +=== tests/cases/compiler/numericEnumMappedType.ts === +// Repro from #31771 + +enum E1 { ONE, TWO, THREE } +>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0)) +>ONE : Symbol(E1.ONE, Decl(numericEnumMappedType.ts, 2, 9)) +>TWO : Symbol(E1.TWO, Decl(numericEnumMappedType.ts, 2, 14)) +>THREE : Symbol(E1.THREE, Decl(numericEnumMappedType.ts, 2, 19)) + +declare enum E2 { ONE, TWO, THREE } +>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27)) +>ONE : Symbol(E2.ONE, Decl(numericEnumMappedType.ts, 3, 17)) +>TWO : Symbol(E2.TWO, Decl(numericEnumMappedType.ts, 3, 22)) +>THREE : Symbol(E2.THREE, Decl(numericEnumMappedType.ts, 3, 27)) + +type Bins1 = { [k in E1]?: string; } +>Bins1 : Symbol(Bins1, Decl(numericEnumMappedType.ts, 3, 35)) +>k : Symbol(k, Decl(numericEnumMappedType.ts, 5, 16)) +>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0)) + +type Bins2 = { [k in E2]?: string; } +>Bins2 : Symbol(Bins2, Decl(numericEnumMappedType.ts, 5, 36)) +>k : Symbol(k, Decl(numericEnumMappedType.ts, 6, 16)) +>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27)) + +const b1: Bins1 = {}; +>b1 : Symbol(b1, Decl(numericEnumMappedType.ts, 8, 5)) +>Bins1 : Symbol(Bins1, Decl(numericEnumMappedType.ts, 3, 35)) + +const b2: Bins2 = {}; +>b2 : Symbol(b2, Decl(numericEnumMappedType.ts, 9, 5)) +>Bins2 : Symbol(Bins2, Decl(numericEnumMappedType.ts, 5, 36)) + +const e1: E1 = E1.ONE; +>e1 : Symbol(e1, Decl(numericEnumMappedType.ts, 11, 5)) +>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0)) +>E1.ONE : Symbol(E1.ONE, Decl(numericEnumMappedType.ts, 2, 9)) +>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0)) +>ONE : Symbol(E1.ONE, Decl(numericEnumMappedType.ts, 2, 9)) + +const e2: E2 = E2.ONE; +>e2 : Symbol(e2, Decl(numericEnumMappedType.ts, 12, 5)) +>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27)) +>E2.ONE : Symbol(E2.ONE, Decl(numericEnumMappedType.ts, 3, 17)) +>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27)) +>ONE : Symbol(E2.ONE, Decl(numericEnumMappedType.ts, 3, 17)) + +b1[1] = "a"; +>b1 : Symbol(b1, Decl(numericEnumMappedType.ts, 8, 5)) +>1 : Symbol(1) + +b1[e1] = "b"; +>b1 : Symbol(b1, Decl(numericEnumMappedType.ts, 8, 5)) +>e1 : Symbol(e1, Decl(numericEnumMappedType.ts, 11, 5)) + +b2[1] = "a"; +>b2 : Symbol(b2, Decl(numericEnumMappedType.ts, 9, 5)) + +b2[e2] = "b"; +>b2 : Symbol(b2, Decl(numericEnumMappedType.ts, 9, 5)) +>e2 : Symbol(e2, Decl(numericEnumMappedType.ts, 12, 5)) + +// Multiple numeric enum types accrue to the same numeric index signature in a mapped type + +declare function val(): number; +>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13)) + +enum N1 { A = val(), B = val() } +>N1 : Symbol(N1, Decl(numericEnumMappedType.ts, 22, 31)) +>A : Symbol(N1.A, Decl(numericEnumMappedType.ts, 24, 9)) +>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13)) +>B : Symbol(N1.B, Decl(numericEnumMappedType.ts, 24, 20)) +>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13)) + +enum N2 { C = val(), D = val() } +>N2 : Symbol(N2, Decl(numericEnumMappedType.ts, 24, 32)) +>C : Symbol(N2.C, Decl(numericEnumMappedType.ts, 25, 9)) +>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13)) +>D : Symbol(N2.D, Decl(numericEnumMappedType.ts, 25, 20)) +>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13)) + +type T1 = { [K in N1 | N2]: K }; +>T1 : Symbol(T1, Decl(numericEnumMappedType.ts, 25, 32)) +>K : Symbol(K, Decl(numericEnumMappedType.ts, 27, 13)) +>N1 : Symbol(N1, Decl(numericEnumMappedType.ts, 22, 31)) +>N2 : Symbol(N2, Decl(numericEnumMappedType.ts, 24, 32)) +>K : Symbol(K, Decl(numericEnumMappedType.ts, 27, 13)) + +// Enum types with string valued members are always literal enum types and therefore +// ONE and TWO below are not computed members but rather just numerically valued members +// with auto-incremented values. + +declare enum E { ONE, TWO, THREE = 'x' } +>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32)) +>ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16)) +>TWO : Symbol(E.TWO, Decl(numericEnumMappedType.ts, 33, 21)) +>THREE : Symbol(E.THREE, Decl(numericEnumMappedType.ts, 33, 26)) + +const e: E = E.ONE; +>e : Symbol(e, Decl(numericEnumMappedType.ts, 34, 5)) +>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32)) +>E.ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16)) +>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32)) +>ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16)) + +const x: E.ONE = e; +>x : Symbol(x, Decl(numericEnumMappedType.ts, 35, 5)) +>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32)) +>ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16)) +>e : Symbol(e, Decl(numericEnumMappedType.ts, 34, 5)) + diff --git a/tests/baselines/reference/numericEnumMappedType.types b/tests/baselines/reference/numericEnumMappedType.types new file mode 100644 index 0000000000000..bffec2ed8ca18 --- /dev/null +++ b/tests/baselines/reference/numericEnumMappedType.types @@ -0,0 +1,117 @@ +=== tests/cases/compiler/numericEnumMappedType.ts === +// Repro from #31771 + +enum E1 { ONE, TWO, THREE } +>E1 : E1 +>ONE : E1.ONE +>TWO : E1.TWO +>THREE : E1.THREE + +declare enum E2 { ONE, TWO, THREE } +>E2 : E2 +>ONE : E2 +>TWO : E2 +>THREE : E2 + +type Bins1 = { [k in E1]?: string; } +>Bins1 : Bins1 + +type Bins2 = { [k in E2]?: string; } +>Bins2 : Bins2 + +const b1: Bins1 = {}; +>b1 : Bins1 +>{} : {} + +const b2: Bins2 = {}; +>b2 : Bins2 +>{} : {} + +const e1: E1 = E1.ONE; +>e1 : E1 +>E1.ONE : E1.ONE +>E1 : typeof E1 +>ONE : E1.ONE + +const e2: E2 = E2.ONE; +>e2 : E2 +>E2.ONE : E2 +>E2 : typeof E2 +>ONE : E2 + +b1[1] = "a"; +>b1[1] = "a" : "a" +>b1[1] : string | undefined +>b1 : Bins1 +>1 : 1 +>"a" : "a" + +b1[e1] = "b"; +>b1[e1] = "b" : "b" +>b1[e1] : string | undefined +>b1 : Bins1 +>e1 : E1.ONE +>"b" : "b" + +b2[1] = "a"; +>b2[1] = "a" : "a" +>b2[1] : string | undefined +>b2 : Bins2 +>1 : 1 +>"a" : "a" + +b2[e2] = "b"; +>b2[e2] = "b" : "b" +>b2[e2] : string | undefined +>b2 : Bins2 +>e2 : E2 +>"b" : "b" + +// Multiple numeric enum types accrue to the same numeric index signature in a mapped type + +declare function val(): number; +>val : () => number + +enum N1 { A = val(), B = val() } +>N1 : N1 +>A : N1 +>val() : number +>val : () => number +>B : N1 +>val() : number +>val : () => number + +enum N2 { C = val(), D = val() } +>N2 : N2 +>C : N2 +>val() : number +>val : () => number +>D : N2 +>val() : number +>val : () => number + +type T1 = { [K in N1 | N2]: K }; +>T1 : T1 + +// Enum types with string valued members are always literal enum types and therefore +// ONE and TWO below are not computed members but rather just numerically valued members +// with auto-incremented values. + +declare enum E { ONE, TWO, THREE = 'x' } +>E : E +>ONE : E.ONE +>TWO : E.TWO +>THREE : E.THREE +>'x' : "x" + +const e: E = E.ONE; +>e : E +>E.ONE : E.ONE +>E : typeof E +>ONE : E.ONE + +const x: E.ONE = e; +>x : E.ONE +>E : any +>e : E.ONE + diff --git a/tests/cases/compiler/numericEnumMappedType.ts b/tests/cases/compiler/numericEnumMappedType.ts new file mode 100644 index 0000000000000..7c03991e6a206 --- /dev/null +++ b/tests/cases/compiler/numericEnumMappedType.ts @@ -0,0 +1,39 @@ +// @strict: true +// @declaration: true + +// Repro from #31771 + +enum E1 { ONE, TWO, THREE } +declare enum E2 { ONE, TWO, THREE } + +type Bins1 = { [k in E1]?: string; } +type Bins2 = { [k in E2]?: string; } + +const b1: Bins1 = {}; +const b2: Bins2 = {}; + +const e1: E1 = E1.ONE; +const e2: E2 = E2.ONE; + +b1[1] = "a"; +b1[e1] = "b"; + +b2[1] = "a"; +b2[e2] = "b"; + +// Multiple numeric enum types accrue to the same numeric index signature in a mapped type + +declare function val(): number; + +enum N1 { A = val(), B = val() } +enum N2 { C = val(), D = val() } + +type T1 = { [K in N1 | N2]: K }; + +// Enum types with string valued members are always literal enum types and therefore +// ONE and TWO below are not computed members but rather just numerically valued members +// with auto-incremented values. + +declare enum E { ONE, TWO, THREE = 'x' } +const e: E = E.ONE; +const x: E.ONE = e;