@@ -37,12 +37,50 @@ 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 ) ;
4246 }
47+
4348 return ts . visitEachChild ( node , visitNode , context ) ;
4449 }
4550
51+ function isMapForOfStatement ( node : ts . Node ) : node is ts . ForOfStatement {
52+ if ( ! ts . isForOfStatement ( node ) || node . awaitModifier != null ) {
53+ return false ;
54+ }
55+
56+ const { expression } = node ;
57+
58+ const expressionType = typeChecker . getTypeAtLocation ( expression ) ;
59+
60+ for ( const subType of unionTypeParts ( expressionType ) ) {
61+ assert (
62+ ! ( subType . flags & ( ts . TypeFlags . Any | ts . TypeFlags . Unknown ) ) ,
63+ 'Can not use any or unknown values in for-of loop: ' +
64+ nodeLocationString ( node ) ,
65+ ) ;
66+
67+ if ( subType . flags & ts . TypeFlags . StringLike ) {
68+ continue ;
69+ }
70+
71+ const typeName = subType . getSymbol ( ) ?. getName ( ) ;
72+ assert ( typeName != null ) ;
73+
74+ if ( typeName === 'Map' ) {
75+ continue ;
76+ }
77+
78+ return false ;
79+ }
80+
81+ return true ;
82+ }
83+
4684 function isArrayForOfStatement ( node : ts . Node ) : node is ts . ForOfStatement {
4785 if ( ! ts . isForOfStatement ( node ) || node . awaitModifier != null ) {
4886 return false ;
@@ -91,6 +129,78 @@ export function optimizeForOf(program: ts.Program) {
91129 return ( type . flags & ts . TypeFlags . Union ) !== 0 ;
92130 }
93131
132+ function convertForOfStatementForMap (
133+ forOfNode : ts . ForOfStatement ,
134+ ) : ts . Statement {
135+ const counter = factory . createLoopVariable ( ) ;
136+ const forDeclarations = [
137+ factory . createVariableDeclaration (
138+ counter ,
139+ undefined , // exclamationToken
140+ undefined , // type
141+ factory . createNumericLiteral ( 0 ) ,
142+ ) ,
143+ ] ;
144+
145+ // In the case where the user wrote an identifier as the RHS, like this:
146+ //
147+ // for (let v of arr) { }
148+ //
149+ // we don't want to emit a temporary variable for the RHS, just use it directly.
150+ const variableDeclarations = [ ]
151+ let reference : ts . Identifier
152+ if ( ts . isIdentifier ( forOfNode . expression ) ) {
153+ reference = forOfNode . expression ;
154+ } else {
155+ const temp = factory . createTempVariable (
156+ ( identifier ) => reference = identifier , // recordTempVariable
157+ ) ;
158+ variableDeclarations . push (
159+ factory . createVariableDeclaration (
160+ temp ,
161+ undefined , // exclamationToken
162+ undefined , // type
163+ forOfNode . expression ,
164+ ) )
165+ }
166+ const valuesExpression = factory . createCallExpression ( factory . createPropertyAccessExpression ( reference ! , factory . createIdentifier ( 'values' ) ) , undefined , [ ] ) ;
167+ variableDeclarations . push ( factory . createVariableDeclaration ( `_${ reference ! . text } ` , undefined , undefined , valuesExpression ) ) ;
168+ const rhsReference = factory . createIdentifier ( `_${ reference ! . text } ` ) ;
169+
170+ const forInitializer = factory . createVariableDeclarationList (
171+ forDeclarations ,
172+ ts . NodeFlags . Let ,
173+ ) ;
174+
175+ const forCondition = factory . createLessThan (
176+ counter ,
177+ factory . createPropertyAccessExpression ( rhsReference , 'length' ) ,
178+ ) ;
179+ const forIncrementor = factory . createPostfixIncrement ( counter ) ;
180+
181+ assert ( ts . isVariableDeclarationList ( forOfNode . initializer ) ) ;
182+ // It will use rhsIterationValue _a[_i] as the initializer.
183+ const itemAssignment = convertForOfInitializer (
184+ forOfNode . initializer ,
185+ factory . createElementAccessExpression ( rhsReference , counter ) ,
186+ ) ;
187+
188+ assert ( ts . isBlock ( forOfNode . statement ) ) ;
189+ const forBody = factory . updateBlock ( forOfNode . statement , [
190+ itemAssignment ,
191+ ...forOfNode . statement . statements ,
192+ ] ) ;
193+
194+ const forStatement = factory . createForStatement (
195+ forInitializer ,
196+ forCondition ,
197+ forIncrementor ,
198+ forBody ,
199+ ) ;
200+
201+ return factory . createBlock ( [ factory . createVariableStatement ( undefined , variableDeclarations ) , forStatement ] ) ;
202+ }
203+
94204 function convertForOfStatementForArray (
95205 forOfNode : ts . ForOfStatement ,
96206 ) : ts . Statement {
@@ -126,7 +236,7 @@ export function optimizeForOf(program: ts.Program) {
126236 ) ;
127237 }
128238
129- const forInitiliazer = factory . createVariableDeclarationList (
239+ const forInitializer = factory . createVariableDeclarationList (
130240 forDeclarations ,
131241 ts . NodeFlags . Let ,
132242 ) ;
@@ -151,7 +261,7 @@ export function optimizeForOf(program: ts.Program) {
151261 ] ) ;
152262
153263 return factory . createForStatement (
154- forInitiliazer ,
264+ forInitializer ,
155265 forCondition ,
156266 forIncrementor ,
157267 forBody ,
0 commit comments