From 2e99438edc937d6001a8047eb75ea82407207a23 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 5 Jun 2019 14:57:18 -0700 Subject: [PATCH 1/6] Handle numeric enums in mapped types + fix obscure crash --- src/compiler/checker.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ed6768cb3d72..01cb7c9fea2b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6444,7 +6444,7 @@ 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 memberType = getFreshTypeOfLiteralType(getLiteralType(getEnumMemberValue(member) || 0, enumCount, getSymbolOfNode(member))); getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } @@ -7453,8 +7453,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 +28441,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. From 5ad46b180b0ec0b1b51df234acf0d5ededf457c3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 5 Jun 2019 16:53:42 -0700 Subject: [PATCH 2/6] Fix minor issue --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 01cb7c9fea2b7..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) || 0, enumCount, getSymbolOfNode(member))); + const value = getEnumMemberValue(member); + const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } From f8aaccdd1d64dca3e3d99a02643264b9f17a05e7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 5 Jun 2019 16:55:08 -0700 Subject: [PATCH 3/6] Add tests --- tests/cases/compiler/numericEnumMappedType.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/cases/compiler/numericEnumMappedType.ts diff --git a/tests/cases/compiler/numericEnumMappedType.ts b/tests/cases/compiler/numericEnumMappedType.ts new file mode 100644 index 0000000000000..be29167bb3d39 --- /dev/null +++ b/tests/cases/compiler/numericEnumMappedType.ts @@ -0,0 +1,38 @@ +// @strict: 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; From fb8216bca024b7a37b7643472942901936886aa2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 5 Jun 2019 16:55:16 -0700 Subject: [PATCH 4/6] Accept new baselines --- .../reference/numericEnumMappedType.js | 68 ++++++++++ .../reference/numericEnumMappedType.symbols | 111 +++++++++++++++++ .../reference/numericEnumMappedType.types | 117 ++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 tests/baselines/reference/numericEnumMappedType.js create mode 100644 tests/baselines/reference/numericEnumMappedType.symbols create mode 100644 tests/baselines/reference/numericEnumMappedType.types diff --git a/tests/baselines/reference/numericEnumMappedType.js b/tests/baselines/reference/numericEnumMappedType.js new file mode 100644 index 0000000000000..555ec9712dbf9 --- /dev/null +++ b/tests/baselines/reference/numericEnumMappedType.js @@ -0,0 +1,68 @@ +//// [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; 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 + From 7e07669885437103c06ff6832736c88120eccc67 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Jun 2019 09:41:44 -0700 Subject: [PATCH 5/6] Generate declaration file from tests --- tests/cases/compiler/numericEnumMappedType.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/compiler/numericEnumMappedType.ts b/tests/cases/compiler/numericEnumMappedType.ts index be29167bb3d39..7c03991e6a206 100644 --- a/tests/cases/compiler/numericEnumMappedType.ts +++ b/tests/cases/compiler/numericEnumMappedType.ts @@ -1,4 +1,5 @@ // @strict: true +// @declaration: true // Repro from #31771 From 50a6002d8ceabf752d68ff67e0b73f3e8ff3b97c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 6 Jun 2019 09:41:52 -0700 Subject: [PATCH 6/6] Accept new baselines --- .../reference/numericEnumMappedType.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/baselines/reference/numericEnumMappedType.js b/tests/baselines/reference/numericEnumMappedType.js index 555ec9712dbf9..78a13e443ff42 100644 --- a/tests/baselines/reference/numericEnumMappedType.js +++ b/tests/baselines/reference/numericEnumMappedType.js @@ -66,3 +66,45 @@ var N2; })(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;