@@ -37,12 +37,85 @@ export function optimizeForOf(program: ts.Program) {
3737 return ts . visitNode ( sourceFile , visitNode ) as ts . SourceFile ;
3838
3939 function visitNode ( node : ts . Node ) : ts . Node {
40+ // TODO: add Set support
41+ // TODO: potentially add support for for..in statements as well
4042 if ( isArrayForOfStatement ( node ) ) {
4143 return convertForOfStatementForArray ( node ) ;
44+ } else if ( isMapForOfStatement ( node ) ) {
45+ return convertForOfStatementForMap ( node ) ;
46+ } else if ( isSetForOfStatement ( node ) ) {
47+ return convertForOfStatementForSet ( node ) ;
4248 }
49+
4350 return ts . visitEachChild ( node , visitNode , context ) ;
4451 }
4552
53+ function isSetForOfStatement ( node : ts . Node ) : node is ts . ForOfStatement {
54+ if ( ! ts . isForOfStatement ( node ) || node . awaitModifier != null ) {
55+ return false ;
56+ }
57+
58+ const { expression } = node ;
59+
60+ const expressionType = typeChecker . getTypeAtLocation ( expression ) ;
61+
62+ for ( const subType of unionTypeParts ( expressionType ) ) {
63+ assert (
64+ ! ( subType . flags & ( ts . TypeFlags . Any | ts . TypeFlags . Unknown ) ) ,
65+ 'Can not use any or unknown values in for-of loop: ' +
66+ nodeLocationString ( node ) ,
67+ ) ;
68+
69+ if ( subType . flags & ts . TypeFlags . StringLike ) {
70+ continue ;
71+ }
72+
73+ const typeName = subType . getSymbol ( ) ?. getName ( ) ;
74+ assert ( typeName != null ) ;
75+
76+ if ( typeName === 'Set' ) {
77+ continue ;
78+ }
79+
80+ return false ;
81+ }
82+
83+ return true ;
84+ }
85+
86+ function isMapForOfStatement ( node : ts . Node ) : node is ts . ForOfStatement {
87+ if ( ! ts . isForOfStatement ( node ) || node . awaitModifier != null ) {
88+ return false ;
89+ }
90+
91+ const { expression } = node ;
92+
93+ const expressionType = typeChecker . getTypeAtLocation ( expression ) ;
94+
95+ for ( const subType of unionTypeParts ( expressionType ) ) {
96+ assert (
97+ ! ( subType . flags & ( ts . TypeFlags . Any | ts . TypeFlags . Unknown ) ) ,
98+ 'Can not use any or unknown values in for-of loop: ' +
99+ nodeLocationString ( node ) ,
100+ ) ;
101+
102+ if ( subType . flags & ts . TypeFlags . StringLike ) {
103+ continue ;
104+ }
105+
106+ const typeName = subType . getSymbol ( ) ?. getName ( ) ;
107+ assert ( typeName != null ) ;
108+
109+ if ( typeName === 'Map' ) {
110+ continue ;
111+ }
112+
113+ return false ;
114+ }
115+
116+ return true ;
117+ }
118+
46119 function isArrayForOfStatement ( node : ts . Node ) : node is ts . ForOfStatement {
47120 if ( ! ts . isForOfStatement ( node ) || node . awaitModifier != null ) {
48121 return false ;
@@ -91,6 +164,190 @@ export function optimizeForOf(program: ts.Program) {
91164 return ( type . flags & ts . TypeFlags . Union ) !== 0 ;
92165 }
93166
167+ function convertForOfStatementForSet (
168+ forOfNode : ts . ForOfStatement ,
169+ ) : ts . Statement {
170+ const counter = factory . createLoopVariable ( ) ;
171+ const forDeclarations = [
172+ factory . createVariableDeclaration (
173+ counter ,
174+ undefined , // exclamationToken
175+ undefined , // type
176+ factory . createNumericLiteral ( 0 ) ,
177+ ) ,
178+ ] ;
179+
180+ // In the case where the user wrote an identifier as the RHS, like this:
181+ //
182+ // for (let v of arr) { }
183+ //
184+ // we don't want to emit a temporary variable for the RHS, just use it directly.
185+ const variableDeclarations = [ ] ;
186+ let reference : ts . Identifier = factory . createIdentifier ( 'temp' ) ;
187+ if ( ts . isIdentifier ( forOfNode . expression ) ) {
188+ reference = forOfNode . expression ;
189+ } else {
190+ const temp = factory . createTempVariable (
191+ ( identifier ) => ( reference = identifier ) , // recordTempVariable
192+ ) ;
193+ variableDeclarations . push (
194+ factory . createVariableDeclaration (
195+ temp ,
196+ undefined , // exclamationToken
197+ undefined , // type
198+ forOfNode . expression ,
199+ ) ,
200+ ) ;
201+ }
202+
203+ const tempText = `_${ reference . text } ` ;
204+ const valuesExpression = factory . createCallExpression (
205+ factory . createPropertyAccessExpression (
206+ reference ,
207+ factory . createIdentifier ( 'values' ) ,
208+ ) ,
209+ undefined ,
210+ [ ] ,
211+ ) ;
212+ variableDeclarations . push (
213+ factory . createVariableDeclaration (
214+ tempText ,
215+ undefined ,
216+ undefined ,
217+ valuesExpression ,
218+ ) ,
219+ ) ;
220+ const rhsReference = factory . createIdentifier ( tempText ) ;
221+
222+ const forInitializer = factory . createVariableDeclarationList (
223+ forDeclarations ,
224+ ts . NodeFlags . Let ,
225+ ) ;
226+
227+ const forCondition = factory . createLessThan (
228+ counter ,
229+ factory . createPropertyAccessExpression ( rhsReference , 'length' ) ,
230+ ) ;
231+ const forIncrementor = factory . createPostfixIncrement ( counter ) ;
232+
233+ assert ( ts . isVariableDeclarationList ( forOfNode . initializer ) ) ;
234+ // It will use rhsIterationValue _a[_i] as the initializer.
235+ const itemAssignment = convertForOfInitializer (
236+ forOfNode . initializer ,
237+ factory . createElementAccessExpression ( rhsReference , counter ) ,
238+ ) ;
239+
240+ assert ( ts . isBlock ( forOfNode . statement ) ) ;
241+ const forBody = factory . updateBlock ( forOfNode . statement , [
242+ itemAssignment ,
243+ ...forOfNode . statement . statements ,
244+ ] ) ;
245+
246+ const forStatement = factory . createForStatement (
247+ forInitializer ,
248+ forCondition ,
249+ forIncrementor ,
250+ forBody ,
251+ ) ;
252+
253+ return factory . createBlock ( [
254+ factory . createVariableStatement ( undefined , variableDeclarations ) ,
255+ forStatement ,
256+ ] ) ;
257+ }
258+
259+ function convertForOfStatementForMap (
260+ forOfNode : ts . ForOfStatement ,
261+ ) : ts . Statement {
262+ const counter = factory . createLoopVariable ( ) ;
263+ const forDeclarations = [
264+ factory . createVariableDeclaration (
265+ counter ,
266+ undefined , // exclamationToken
267+ undefined , // type
268+ factory . createNumericLiteral ( 0 ) ,
269+ ) ,
270+ ] ;
271+
272+ // In the case where the user wrote an identifier as the RHS, like this:
273+ //
274+ // for (let v of arr) { }
275+ //
276+ // we don't want to emit a temporary variable for the RHS, just use it directly.
277+ const variableDeclarations = [ ] ;
278+ let reference : ts . Identifier = factory . createIdentifier ( 'temp' ) ;
279+ if ( ts . isIdentifier ( forOfNode . expression ) ) {
280+ reference = forOfNode . expression ;
281+ } else {
282+ const temp = factory . createTempVariable (
283+ ( identifier ) => ( reference = identifier ) , // recordTempVariable
284+ ) ;
285+ variableDeclarations . push (
286+ factory . createVariableDeclaration (
287+ temp ,
288+ undefined , // exclamationToken
289+ undefined , // type
290+ forOfNode . expression ,
291+ ) ,
292+ ) ;
293+ }
294+
295+ const tempText = `_${ reference . text } ` ;
296+ const valuesExpression = factory . createCallExpression (
297+ factory . createPropertyAccessExpression (
298+ reference ,
299+ factory . createIdentifier ( 'values' ) ,
300+ ) ,
301+ undefined ,
302+ [ ] ,
303+ ) ;
304+ variableDeclarations . push (
305+ factory . createVariableDeclaration (
306+ tempText ,
307+ undefined ,
308+ undefined ,
309+ valuesExpression ,
310+ ) ,
311+ ) ;
312+ const rhsReference = factory . createIdentifier ( tempText ) ;
313+
314+ const forInitializer = factory . createVariableDeclarationList (
315+ forDeclarations ,
316+ ts . NodeFlags . Let ,
317+ ) ;
318+
319+ const forCondition = factory . createLessThan (
320+ counter ,
321+ factory . createPropertyAccessExpression ( rhsReference , 'length' ) ,
322+ ) ;
323+ const forIncrementor = factory . createPostfixIncrement ( counter ) ;
324+
325+ assert ( ts . isVariableDeclarationList ( forOfNode . initializer ) ) ;
326+ // It will use rhsIterationValue _a[_i] as the initializer.
327+ const itemAssignment = convertForOfInitializer (
328+ forOfNode . initializer ,
329+ factory . createElementAccessExpression ( rhsReference , counter ) ,
330+ ) ;
331+
332+ assert ( ts . isBlock ( forOfNode . statement ) ) ;
333+ const forBody = factory . updateBlock ( forOfNode . statement , [
334+ itemAssignment ,
335+ ...forOfNode . statement . statements ,
336+ ] ) ;
337+
338+ const forStatement = factory . createForStatement (
339+ forInitializer ,
340+ forCondition ,
341+ forIncrementor ,
342+ forBody ,
343+ ) ;
344+
345+ return factory . createBlock ( [
346+ factory . createVariableStatement ( undefined , variableDeclarations ) ,
347+ forStatement ,
348+ ] ) ;
349+ }
350+
94351 function convertForOfStatementForArray (
95352 forOfNode : ts . ForOfStatement ,
96353 ) : ts . Statement {
@@ -126,7 +383,7 @@ export function optimizeForOf(program: ts.Program) {
126383 ) ;
127384 }
128385
129- const forInitiliazer = factory . createVariableDeclarationList (
386+ const forInitializer = factory . createVariableDeclarationList (
130387 forDeclarations ,
131388 ts . NodeFlags . Let ,
132389 ) ;
@@ -151,7 +408,7 @@ export function optimizeForOf(program: ts.Program) {
151408 ] ) ;
152409
153410 return factory . createForStatement (
154- forInitiliazer ,
411+ forInitializer ,
155412 forCondition ,
156413 forIncrementor ,
157414 forBody ,
0 commit comments