Skip to content

Commit 5b2b6c5

Browse files
TimmmmTim Hutt
authored and
Tim Hutt
committed
Add support for iterating over enums
I'm fairly confident about the codegen stuff, but not about the type checker. Iterating over `const enum`s is not supported. This includes a simple test case.
1 parent 6ed344f commit 5b2b6c5

11 files changed

+341
-5
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35078,6 +35078,17 @@ namespace ts {
3507835078
return anyIterationTypes;
3507935079
}
3508035080

35081+
const symbolType = getDeclaredTypeOfSymbol(type.symbol);
35082+
35083+
if (symbolType.symbol.flags & SymbolFlags.RegularEnum) {
35084+
return createIterationTypes(
35085+
createTupleType([
35086+
stringType,
35087+
symbolType,
35088+
]),
35089+
);
35090+
}
35091+
3508135092
if (use & IterationUse.AllowsAsyncIterablesFlag) {
3508235093
const iterationTypes =
3508335094
getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) ||

src/compiler/transformers/ts.ts

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,7 +2425,20 @@ namespace ts {
24252425

24262426
const statements: Statement[] = [];
24272427
startLexicalEnvironment();
2428-
const members = map(node.members, transformEnumMember);
2428+
const membersAndExpressions = node.members.map(member => {
2429+
const [nameExpression, valueExpression] = getEnumMemberExpressions(member);
2430+
return {
2431+
member,
2432+
nameExpression,
2433+
valueExpression,
2434+
};
2435+
});
2436+
const members = membersAndExpressions.map(m => transformEnumMember(
2437+
m.member,
2438+
m.nameExpression,
2439+
m.valueExpression,
2440+
));
2441+
members.push(transformEnumMembersToIterator(membersAndExpressions));
24292442
insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());
24302443
addRange(statements, members);
24312444

@@ -2437,20 +2450,37 @@ namespace ts {
24372450
}
24382451

24392452
/**
2440-
* Transforms an enum member into a statement.
2453+
* Get an expression for the name and value of an enum member.
2454+
* For example `enum Maybe { Yes = 1, No = 2 }` would give
2455+
* [`"Yes"`, `1`] and [`"No"`, `2`] as the expressions.
24412456
*
24422457
* @param member The enum member node.
24432458
*/
2444-
function transformEnumMember(member: EnumMember): Statement {
2459+
function getEnumMemberExpressions(member: EnumMember): [Expression, Expression] {
24452460
// enums don't support computed properties
24462461
// we pass false as 'generateNameForComputedPropertyName' for a backward compatibility purposes
24472462
// old emitter always generate 'expression' part of the name as-is.
24482463
const name = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ false);
24492464
const valueExpression = transformEnumMemberDeclarationValue(member);
2465+
return [name, valueExpression];
2466+
}
2467+
2468+
/**
2469+
* Transforms an enum member into a statement.
2470+
*
2471+
* @param member The enum member node.
2472+
* @param nameExpression A string literal expression containing the member name
2473+
* @param valueExpression An expression containing the member value.
2474+
*/
2475+
function transformEnumMember(
2476+
member: EnumMember,
2477+
nameExpression: Expression,
2478+
valueExpression: Expression,
2479+
): Statement {
24502480
const innerAssignment = factory.createAssignment(
24512481
factory.createElementAccessExpression(
24522482
currentNamespaceContainerName,
2453-
name
2483+
nameExpression
24542484
),
24552485
valueExpression
24562486
);
@@ -2461,7 +2491,7 @@ namespace ts {
24612491
currentNamespaceContainerName,
24622492
innerAssignment
24632493
),
2464-
name
2494+
nameExpression
24652495
);
24662496
return setTextRange(
24672497
factory.createExpressionStatement(
@@ -2474,6 +2504,56 @@ namespace ts {
24742504
);
24752505
}
24762506

2507+
/**
2508+
* Transforms all enum members into a generator function that yields
2509+
* the enum members.
2510+
*
2511+
* @param members The enum member nodes.
2512+
*/
2513+
function transformEnumMembersToIterator(
2514+
memberExpressions: {
2515+
nameExpression: Expression,
2516+
valueExpression: Expression,
2517+
}[],
2518+
): Statement {
2519+
const symbolIterator = factory.createPropertyAccessExpression(
2520+
factory.createIdentifier("Symbol"),
2521+
"iterator",
2522+
);
2523+
const iteratorMember = factory.createElementAccessExpression(
2524+
currentNamespaceContainerName,
2525+
symbolIterator,
2526+
);
2527+
2528+
const yieldExpression = factory.createYieldExpression(
2529+
factory.createToken(SyntaxKind.AsteriskToken),
2530+
factory.createArrayLiteralExpression(memberExpressions.map(
2531+
expressions => factory.createArrayLiteralExpression([
2532+
expressions.nameExpression,
2533+
expressions.valueExpression,
2534+
]),
2535+
)),
2536+
);
2537+
const generatorBody = factory.createBlock([
2538+
factory.createExpressionStatement(yieldExpression)
2539+
]);
2540+
2541+
const iteratorGenerator = factory.createFunctionExpression(
2542+
/*modifiers*/ undefined,
2543+
factory.createToken(SyntaxKind.AsteriskToken),
2544+
/*name*/ undefined,
2545+
[],
2546+
[],
2547+
/*type*/ undefined,
2548+
generatorBody,
2549+
);
2550+
const outerAssignment = factory.createAssignment(
2551+
iteratorMember,
2552+
iteratorGenerator,
2553+
);
2554+
return factory.createExpressionStatement(outerAssignment);
2555+
}
2556+
24772557
/**
24782558
* Transforms the value of an enum member.
24792559
*
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [enumIteratorBasics1.ts]
2+
enum Test {
3+
Foo,
4+
Bar,
5+
Baz,
6+
}
7+
8+
const AlsoTest = Test;
9+
10+
for (const member of AlsoTest) {
11+
const x: string = member[0];
12+
const y: Test = member[1];
13+
console.log(x, y);
14+
}
15+
16+
const members: [string, Test][] = [...AlsoTest];
17+
console.log(members);
18+
19+
20+
//// [enumIteratorBasics1.js]
21+
var Test;
22+
(function (Test) {
23+
Test[Test["Foo"] = 0] = "Foo";
24+
Test[Test["Bar"] = 1] = "Bar";
25+
Test[Test["Baz"] = 2] = "Baz";
26+
Test[Symbol.iterator] = function* () { yield* [["Foo", 0], ["Bar", 1], ["Baz", 2]]; };
27+
})(Test || (Test = {}));
28+
const AlsoTest = Test;
29+
for (const member of AlsoTest) {
30+
const x = member[0];
31+
const y = member[1];
32+
console.log(x, y);
33+
}
34+
const members = [...AlsoTest];
35+
console.log(members);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=== tests/cases/compiler/enumIteratorBasics1.ts ===
2+
enum Test {
3+
>Test : Symbol(Test, Decl(enumIteratorBasics1.ts, 0, 0))
4+
5+
Foo,
6+
>Foo : Symbol(Test.Foo, Decl(enumIteratorBasics1.ts, 0, 11))
7+
8+
Bar,
9+
>Bar : Symbol(Test.Bar, Decl(enumIteratorBasics1.ts, 1, 6))
10+
11+
Baz,
12+
>Baz : Symbol(Test.Baz, Decl(enumIteratorBasics1.ts, 2, 6))
13+
}
14+
15+
const AlsoTest = Test;
16+
>AlsoTest : Symbol(AlsoTest, Decl(enumIteratorBasics1.ts, 6, 5))
17+
>Test : Symbol(Test, Decl(enumIteratorBasics1.ts, 0, 0))
18+
19+
for (const member of AlsoTest) {
20+
>member : Symbol(member, Decl(enumIteratorBasics1.ts, 8, 10))
21+
>AlsoTest : Symbol(AlsoTest, Decl(enumIteratorBasics1.ts, 6, 5))
22+
23+
const x: string = member[0];
24+
>x : Symbol(x, Decl(enumIteratorBasics1.ts, 9, 7))
25+
>member : Symbol(member, Decl(enumIteratorBasics1.ts, 8, 10))
26+
>0 : Symbol(0)
27+
28+
const y: Test = member[1];
29+
>y : Symbol(y, Decl(enumIteratorBasics1.ts, 10, 7))
30+
>Test : Symbol(Test, Decl(enumIteratorBasics1.ts, 0, 0))
31+
>member : Symbol(member, Decl(enumIteratorBasics1.ts, 8, 10))
32+
>1 : Symbol(1)
33+
34+
console.log(x, y);
35+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
36+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
37+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
38+
>x : Symbol(x, Decl(enumIteratorBasics1.ts, 9, 7))
39+
>y : Symbol(y, Decl(enumIteratorBasics1.ts, 10, 7))
40+
}
41+
42+
const members: [string, Test][] = [...AlsoTest];
43+
>members : Symbol(members, Decl(enumIteratorBasics1.ts, 14, 5))
44+
>Test : Symbol(Test, Decl(enumIteratorBasics1.ts, 0, 0))
45+
>AlsoTest : Symbol(AlsoTest, Decl(enumIteratorBasics1.ts, 6, 5))
46+
47+
console.log(members);
48+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
49+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
50+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
51+
>members : Symbol(members, Decl(enumIteratorBasics1.ts, 14, 5))
52+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/compiler/enumIteratorBasics1.ts ===
2+
enum Test {
3+
>Test : Test
4+
5+
Foo,
6+
>Foo : Test.Foo
7+
8+
Bar,
9+
>Bar : Test.Bar
10+
11+
Baz,
12+
>Baz : Test.Baz
13+
}
14+
15+
const AlsoTest = Test;
16+
>AlsoTest : typeof Test
17+
>Test : typeof Test
18+
19+
for (const member of AlsoTest) {
20+
>member : [string, Test]
21+
>AlsoTest : typeof Test
22+
23+
const x: string = member[0];
24+
>x : string
25+
>member[0] : string
26+
>member : [string, Test]
27+
>0 : 0
28+
29+
const y: Test = member[1];
30+
>y : Test
31+
>member[1] : Test
32+
>member : [string, Test]
33+
>1 : 1
34+
35+
console.log(x, y);
36+
>console.log(x, y) : void
37+
>console.log : (...data: any[]) => void
38+
>console : Console
39+
>log : (...data: any[]) => void
40+
>x : string
41+
>y : Test
42+
}
43+
44+
const members: [string, Test][] = [...AlsoTest];
45+
>members : [string, Test][]
46+
>[...AlsoTest] : [string, Test][]
47+
>...AlsoTest : [string, Test]
48+
>AlsoTest : typeof Test
49+
50+
console.log(members);
51+
>console.log(members) : void
52+
>console.log : (...data: any[]) => void
53+
>console : Console
54+
>log : (...data: any[]) => void
55+
>members : [string, Test][]
56+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
tests/cases/compiler/enumIteratorConstEnum1.ts(8,22): error TS2475: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.
2+
tests/cases/compiler/enumIteratorConstEnum1.ts(8,22): error TS2488: Type 'typeof ConstTest' must have a '[Symbol.iterator]()' method that returns an iterator.
3+
4+
5+
==== tests/cases/compiler/enumIteratorConstEnum1.ts (2 errors) ====
6+
const enum ConstTest {
7+
Foo,
8+
Bar,
9+
Baz,
10+
}
11+
12+
// Should be an error.
13+
for (const member of ConstTest) {
14+
~~~~~~~~~
15+
!!! error TS2475: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.
16+
~~~~~~~~~
17+
!!! error TS2488: Type 'typeof ConstTest' must have a '[Symbol.iterator]()' method that returns an iterator.
18+
}
19+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//// [enumIteratorConstEnum1.ts]
2+
const enum ConstTest {
3+
Foo,
4+
Bar,
5+
Baz,
6+
}
7+
8+
// Should be an error.
9+
for (const member of ConstTest) {
10+
}
11+
12+
13+
//// [enumIteratorConstEnum1.js]
14+
// Should be an error.
15+
for (const member of ConstTest) {
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/compiler/enumIteratorConstEnum1.ts ===
2+
const enum ConstTest {
3+
>ConstTest : Symbol(ConstTest, Decl(enumIteratorConstEnum1.ts, 0, 0))
4+
5+
Foo,
6+
>Foo : Symbol(ConstTest.Foo, Decl(enumIteratorConstEnum1.ts, 0, 22))
7+
8+
Bar,
9+
>Bar : Symbol(ConstTest.Bar, Decl(enumIteratorConstEnum1.ts, 1, 6))
10+
11+
Baz,
12+
>Baz : Symbol(ConstTest.Baz, Decl(enumIteratorConstEnum1.ts, 2, 6))
13+
}
14+
15+
// Should be an error.
16+
for (const member of ConstTest) {
17+
>member : Symbol(member, Decl(enumIteratorConstEnum1.ts, 7, 10))
18+
>ConstTest : Symbol(ConstTest, Decl(enumIteratorConstEnum1.ts, 0, 0))
19+
}
20+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/compiler/enumIteratorConstEnum1.ts ===
2+
const enum ConstTest {
3+
>ConstTest : ConstTest
4+
5+
Foo,
6+
>Foo : ConstTest.Foo
7+
8+
Bar,
9+
>Bar : ConstTest.Bar
10+
11+
Baz,
12+
>Baz : ConstTest.Baz
13+
}
14+
15+
// Should be an error.
16+
for (const member of ConstTest) {
17+
>member : any
18+
>ConstTest : typeof ConstTest
19+
}
20+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @target: ES6
2+
enum Test {
3+
Foo,
4+
Bar,
5+
Baz,
6+
}
7+
8+
const AlsoTest = Test;
9+
10+
for (const member of AlsoTest) {
11+
const x: string = member[0];
12+
const y: Test = member[1];
13+
console.log(x, y);
14+
}
15+
16+
const members: [string, Test][] = [...AlsoTest];
17+
console.log(members);

0 commit comments

Comments
 (0)