Skip to content

Commit 4ae3a54

Browse files
authored
Merge pull request #31784 from microsoft/numericEnumMappedType
Numeric enums as key types in mapped types
2 parents 2fdf7b5 + 50a6002 commit 4ae3a54

File tree

5 files changed

+385
-6
lines changed

5 files changed

+385
-6
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6444,7 +6444,8 @@ namespace ts {
64446444
for (const declaration of symbol.declarations) {
64456445
if (declaration.kind === SyntaxKind.EnumDeclaration) {
64466446
for (const member of (<EnumDeclaration>declaration).members) {
6447-
const memberType = getFreshTypeOfLiteralType(getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member))); // TODO: GH#18217
6447+
const value = getEnumMemberValue(member);
6448+
const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member)));
64486449
getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType;
64496450
memberTypeList.push(getRegularTypeOfLiteralType(memberType));
64506451
}
@@ -7453,8 +7454,9 @@ namespace ts {
74537454
else if (t.flags & (TypeFlags.Any | TypeFlags.String)) {
74547455
stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
74557456
}
7456-
else if (t.flags & TypeFlags.Number) {
7457-
numberIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
7457+
else if (t.flags & (TypeFlags.Number | TypeFlags.Enum)) {
7458+
numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType,
7459+
!!(templateModifiers & MappedTypeModifiers.IncludeReadonly));
74587460
}
74597461
}
74607462
}
@@ -28465,9 +28467,9 @@ namespace ts {
2846528467
if (member.initializer) {
2846628468
return computeConstantValue(member);
2846728469
}
28468-
// In ambient enum declarations that specify no const modifier, enum member declarations that omit
28469-
// a value are considered computed members (as opposed to having auto-incremented values).
28470-
if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent)) {
28470+
// In ambient non-const numeric enum declarations, enum members without initializers are
28471+
// considered computed members (as opposed to having auto-incremented values).
28472+
if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) {
2847128473
return undefined;
2847228474
}
2847328475
// If the member declaration specifies no value, the member is considered a constant enum member.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//// [numericEnumMappedType.ts]
2+
// Repro from #31771
3+
4+
enum E1 { ONE, TWO, THREE }
5+
declare enum E2 { ONE, TWO, THREE }
6+
7+
type Bins1 = { [k in E1]?: string; }
8+
type Bins2 = { [k in E2]?: string; }
9+
10+
const b1: Bins1 = {};
11+
const b2: Bins2 = {};
12+
13+
const e1: E1 = E1.ONE;
14+
const e2: E2 = E2.ONE;
15+
16+
b1[1] = "a";
17+
b1[e1] = "b";
18+
19+
b2[1] = "a";
20+
b2[e2] = "b";
21+
22+
// Multiple numeric enum types accrue to the same numeric index signature in a mapped type
23+
24+
declare function val(): number;
25+
26+
enum N1 { A = val(), B = val() }
27+
enum N2 { C = val(), D = val() }
28+
29+
type T1 = { [K in N1 | N2]: K };
30+
31+
// Enum types with string valued members are always literal enum types and therefore
32+
// ONE and TWO below are not computed members but rather just numerically valued members
33+
// with auto-incremented values.
34+
35+
declare enum E { ONE, TWO, THREE = 'x' }
36+
const e: E = E.ONE;
37+
const x: E.ONE = e;
38+
39+
40+
//// [numericEnumMappedType.js]
41+
"use strict";
42+
// Repro from #31771
43+
var E1;
44+
(function (E1) {
45+
E1[E1["ONE"] = 0] = "ONE";
46+
E1[E1["TWO"] = 1] = "TWO";
47+
E1[E1["THREE"] = 2] = "THREE";
48+
})(E1 || (E1 = {}));
49+
var b1 = {};
50+
var b2 = {};
51+
var e1 = E1.ONE;
52+
var e2 = E2.ONE;
53+
b1[1] = "a";
54+
b1[e1] = "b";
55+
b2[1] = "a";
56+
b2[e2] = "b";
57+
var N1;
58+
(function (N1) {
59+
N1[N1["A"] = val()] = "A";
60+
N1[N1["B"] = val()] = "B";
61+
})(N1 || (N1 = {}));
62+
var N2;
63+
(function (N2) {
64+
N2[N2["C"] = val()] = "C";
65+
N2[N2["D"] = val()] = "D";
66+
})(N2 || (N2 = {}));
67+
var e = E.ONE;
68+
var x = e;
69+
70+
71+
//// [numericEnumMappedType.d.ts]
72+
declare enum E1 {
73+
ONE = 0,
74+
TWO = 1,
75+
THREE = 2
76+
}
77+
declare enum E2 {
78+
ONE,
79+
TWO,
80+
THREE
81+
}
82+
declare type Bins1 = {
83+
[k in E1]?: string;
84+
};
85+
declare type Bins2 = {
86+
[k in E2]?: string;
87+
};
88+
declare const b1: Bins1;
89+
declare const b2: Bins2;
90+
declare const e1: E1;
91+
declare const e2: E2;
92+
declare function val(): number;
93+
declare enum N1 {
94+
A,
95+
B
96+
}
97+
declare enum N2 {
98+
C,
99+
D
100+
}
101+
declare type T1 = {
102+
[K in N1 | N2]: K;
103+
};
104+
declare enum E {
105+
ONE = 0,
106+
TWO = 1,
107+
THREE = "x"
108+
}
109+
declare const e: E;
110+
declare const x: E.ONE;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
=== tests/cases/compiler/numericEnumMappedType.ts ===
2+
// Repro from #31771
3+
4+
enum E1 { ONE, TWO, THREE }
5+
>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0))
6+
>ONE : Symbol(E1.ONE, Decl(numericEnumMappedType.ts, 2, 9))
7+
>TWO : Symbol(E1.TWO, Decl(numericEnumMappedType.ts, 2, 14))
8+
>THREE : Symbol(E1.THREE, Decl(numericEnumMappedType.ts, 2, 19))
9+
10+
declare enum E2 { ONE, TWO, THREE }
11+
>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27))
12+
>ONE : Symbol(E2.ONE, Decl(numericEnumMappedType.ts, 3, 17))
13+
>TWO : Symbol(E2.TWO, Decl(numericEnumMappedType.ts, 3, 22))
14+
>THREE : Symbol(E2.THREE, Decl(numericEnumMappedType.ts, 3, 27))
15+
16+
type Bins1 = { [k in E1]?: string; }
17+
>Bins1 : Symbol(Bins1, Decl(numericEnumMappedType.ts, 3, 35))
18+
>k : Symbol(k, Decl(numericEnumMappedType.ts, 5, 16))
19+
>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0))
20+
21+
type Bins2 = { [k in E2]?: string; }
22+
>Bins2 : Symbol(Bins2, Decl(numericEnumMappedType.ts, 5, 36))
23+
>k : Symbol(k, Decl(numericEnumMappedType.ts, 6, 16))
24+
>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27))
25+
26+
const b1: Bins1 = {};
27+
>b1 : Symbol(b1, Decl(numericEnumMappedType.ts, 8, 5))
28+
>Bins1 : Symbol(Bins1, Decl(numericEnumMappedType.ts, 3, 35))
29+
30+
const b2: Bins2 = {};
31+
>b2 : Symbol(b2, Decl(numericEnumMappedType.ts, 9, 5))
32+
>Bins2 : Symbol(Bins2, Decl(numericEnumMappedType.ts, 5, 36))
33+
34+
const e1: E1 = E1.ONE;
35+
>e1 : Symbol(e1, Decl(numericEnumMappedType.ts, 11, 5))
36+
>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0))
37+
>E1.ONE : Symbol(E1.ONE, Decl(numericEnumMappedType.ts, 2, 9))
38+
>E1 : Symbol(E1, Decl(numericEnumMappedType.ts, 0, 0))
39+
>ONE : Symbol(E1.ONE, Decl(numericEnumMappedType.ts, 2, 9))
40+
41+
const e2: E2 = E2.ONE;
42+
>e2 : Symbol(e2, Decl(numericEnumMappedType.ts, 12, 5))
43+
>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27))
44+
>E2.ONE : Symbol(E2.ONE, Decl(numericEnumMappedType.ts, 3, 17))
45+
>E2 : Symbol(E2, Decl(numericEnumMappedType.ts, 2, 27))
46+
>ONE : Symbol(E2.ONE, Decl(numericEnumMappedType.ts, 3, 17))
47+
48+
b1[1] = "a";
49+
>b1 : Symbol(b1, Decl(numericEnumMappedType.ts, 8, 5))
50+
>1 : Symbol(1)
51+
52+
b1[e1] = "b";
53+
>b1 : Symbol(b1, Decl(numericEnumMappedType.ts, 8, 5))
54+
>e1 : Symbol(e1, Decl(numericEnumMappedType.ts, 11, 5))
55+
56+
b2[1] = "a";
57+
>b2 : Symbol(b2, Decl(numericEnumMappedType.ts, 9, 5))
58+
59+
b2[e2] = "b";
60+
>b2 : Symbol(b2, Decl(numericEnumMappedType.ts, 9, 5))
61+
>e2 : Symbol(e2, Decl(numericEnumMappedType.ts, 12, 5))
62+
63+
// Multiple numeric enum types accrue to the same numeric index signature in a mapped type
64+
65+
declare function val(): number;
66+
>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13))
67+
68+
enum N1 { A = val(), B = val() }
69+
>N1 : Symbol(N1, Decl(numericEnumMappedType.ts, 22, 31))
70+
>A : Symbol(N1.A, Decl(numericEnumMappedType.ts, 24, 9))
71+
>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13))
72+
>B : Symbol(N1.B, Decl(numericEnumMappedType.ts, 24, 20))
73+
>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13))
74+
75+
enum N2 { C = val(), D = val() }
76+
>N2 : Symbol(N2, Decl(numericEnumMappedType.ts, 24, 32))
77+
>C : Symbol(N2.C, Decl(numericEnumMappedType.ts, 25, 9))
78+
>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13))
79+
>D : Symbol(N2.D, Decl(numericEnumMappedType.ts, 25, 20))
80+
>val : Symbol(val, Decl(numericEnumMappedType.ts, 18, 13))
81+
82+
type T1 = { [K in N1 | N2]: K };
83+
>T1 : Symbol(T1, Decl(numericEnumMappedType.ts, 25, 32))
84+
>K : Symbol(K, Decl(numericEnumMappedType.ts, 27, 13))
85+
>N1 : Symbol(N1, Decl(numericEnumMappedType.ts, 22, 31))
86+
>N2 : Symbol(N2, Decl(numericEnumMappedType.ts, 24, 32))
87+
>K : Symbol(K, Decl(numericEnumMappedType.ts, 27, 13))
88+
89+
// Enum types with string valued members are always literal enum types and therefore
90+
// ONE and TWO below are not computed members but rather just numerically valued members
91+
// with auto-incremented values.
92+
93+
declare enum E { ONE, TWO, THREE = 'x' }
94+
>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32))
95+
>ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16))
96+
>TWO : Symbol(E.TWO, Decl(numericEnumMappedType.ts, 33, 21))
97+
>THREE : Symbol(E.THREE, Decl(numericEnumMappedType.ts, 33, 26))
98+
99+
const e: E = E.ONE;
100+
>e : Symbol(e, Decl(numericEnumMappedType.ts, 34, 5))
101+
>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32))
102+
>E.ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16))
103+
>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32))
104+
>ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16))
105+
106+
const x: E.ONE = e;
107+
>x : Symbol(x, Decl(numericEnumMappedType.ts, 35, 5))
108+
>E : Symbol(E, Decl(numericEnumMappedType.ts, 27, 32))
109+
>ONE : Symbol(E.ONE, Decl(numericEnumMappedType.ts, 33, 16))
110+
>e : Symbol(e, Decl(numericEnumMappedType.ts, 34, 5))
111+
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
=== tests/cases/compiler/numericEnumMappedType.ts ===
2+
// Repro from #31771
3+
4+
enum E1 { ONE, TWO, THREE }
5+
>E1 : E1
6+
>ONE : E1.ONE
7+
>TWO : E1.TWO
8+
>THREE : E1.THREE
9+
10+
declare enum E2 { ONE, TWO, THREE }
11+
>E2 : E2
12+
>ONE : E2
13+
>TWO : E2
14+
>THREE : E2
15+
16+
type Bins1 = { [k in E1]?: string; }
17+
>Bins1 : Bins1
18+
19+
type Bins2 = { [k in E2]?: string; }
20+
>Bins2 : Bins2
21+
22+
const b1: Bins1 = {};
23+
>b1 : Bins1
24+
>{} : {}
25+
26+
const b2: Bins2 = {};
27+
>b2 : Bins2
28+
>{} : {}
29+
30+
const e1: E1 = E1.ONE;
31+
>e1 : E1
32+
>E1.ONE : E1.ONE
33+
>E1 : typeof E1
34+
>ONE : E1.ONE
35+
36+
const e2: E2 = E2.ONE;
37+
>e2 : E2
38+
>E2.ONE : E2
39+
>E2 : typeof E2
40+
>ONE : E2
41+
42+
b1[1] = "a";
43+
>b1[1] = "a" : "a"
44+
>b1[1] : string | undefined
45+
>b1 : Bins1
46+
>1 : 1
47+
>"a" : "a"
48+
49+
b1[e1] = "b";
50+
>b1[e1] = "b" : "b"
51+
>b1[e1] : string | undefined
52+
>b1 : Bins1
53+
>e1 : E1.ONE
54+
>"b" : "b"
55+
56+
b2[1] = "a";
57+
>b2[1] = "a" : "a"
58+
>b2[1] : string | undefined
59+
>b2 : Bins2
60+
>1 : 1
61+
>"a" : "a"
62+
63+
b2[e2] = "b";
64+
>b2[e2] = "b" : "b"
65+
>b2[e2] : string | undefined
66+
>b2 : Bins2
67+
>e2 : E2
68+
>"b" : "b"
69+
70+
// Multiple numeric enum types accrue to the same numeric index signature in a mapped type
71+
72+
declare function val(): number;
73+
>val : () => number
74+
75+
enum N1 { A = val(), B = val() }
76+
>N1 : N1
77+
>A : N1
78+
>val() : number
79+
>val : () => number
80+
>B : N1
81+
>val() : number
82+
>val : () => number
83+
84+
enum N2 { C = val(), D = val() }
85+
>N2 : N2
86+
>C : N2
87+
>val() : number
88+
>val : () => number
89+
>D : N2
90+
>val() : number
91+
>val : () => number
92+
93+
type T1 = { [K in N1 | N2]: K };
94+
>T1 : T1
95+
96+
// Enum types with string valued members are always literal enum types and therefore
97+
// ONE and TWO below are not computed members but rather just numerically valued members
98+
// with auto-incremented values.
99+
100+
declare enum E { ONE, TWO, THREE = 'x' }
101+
>E : E
102+
>ONE : E.ONE
103+
>TWO : E.TWO
104+
>THREE : E.THREE
105+
>'x' : "x"
106+
107+
const e: E = E.ONE;
108+
>e : E
109+
>E.ONE : E.ONE
110+
>E : typeof E
111+
>ONE : E.ONE
112+
113+
const x: E.ONE = e;
114+
>x : E.ONE
115+
>E : any
116+
>e : E.ONE
117+

0 commit comments

Comments
 (0)