@@ -33,6 +33,12 @@ export interface Scope {
3333 functionReturned ?: boolean ;
3434}
3535
36+ export interface HoistingResult {
37+ statements : lua . Statement [ ] ;
38+ hoistedStatements : lua . Statement [ ] ;
39+ hoistedIdentifiers : lua . Identifier [ ] ;
40+ }
41+
3642const scopeStacks = new WeakMap < TransformationContext , Scope [ ] > ( ) ;
3743function getScopeStack ( context : TransformationContext ) : Scope [ ] {
3844 return getOrUpdate ( scopeStacks , context , ( ) => [ ] ) ;
@@ -133,16 +139,47 @@ export function isFunctionScopeWithDefinition(scope: Scope): scope is Scope & {
133139 return scope . node !== undefined && ts . isFunctionLike ( scope . node ) ;
134140}
135141
136- export function performHoisting ( context : TransformationContext , statements : lua . Statement [ ] ) : lua . Statement [ ] {
142+ export function separateHoistedStatements ( context : TransformationContext , statements : lua . Statement [ ] ) : HoistingResult {
137143 const scope = peekScope ( context ) ;
138- let result = statements ;
139- result = hoistFunctionDefinitions ( context , scope , result ) ;
140- result = hoistVariableDeclarations ( context , scope , result ) ;
141- result = hoistImportStatements ( scope , result ) ;
142- return result ;
144+ const allHoistedStatments : lua . Statement [ ] = [ ] ;
145+ const allHoistedIdentifiers : lua . Identifier [ ] = [ ] ;
146+
147+ let { unhoistedStatements, hoistedStatements, hoistedIdentifiers } = hoistFunctionDefinitions (
148+ context ,
149+ scope ,
150+ statements
151+ ) ;
152+ allHoistedStatments . push ( ...hoistedStatements ) ;
153+ allHoistedIdentifiers . push ( ...hoistedIdentifiers ) ;
154+
155+ ( { unhoistedStatements, hoistedIdentifiers } = hoistVariableDeclarations ( context , scope , unhoistedStatements ) ) ;
156+ allHoistedIdentifiers . push ( ...hoistedIdentifiers ) ;
157+
158+ ( { unhoistedStatements, hoistedStatements } = hoistImportStatements ( scope , unhoistedStatements ) ) ;
159+ allHoistedStatments . unshift ( ...hoistedStatements ) ;
160+
161+ return {
162+ statements : unhoistedStatements ,
163+ hoistedStatements : allHoistedStatments ,
164+ hoistedIdentifiers : allHoistedIdentifiers ,
165+ } ;
166+ }
167+
168+ export function performHoisting ( context : TransformationContext , statements : lua . Statement [ ] ) : lua . Statement [ ] {
169+ const result = separateHoistedStatements ( context , statements ) ;
170+ const modifiedStatements = [ ...result . hoistedStatements , ...result . statements ] ;
171+ if ( result . hoistedIdentifiers . length > 0 ) {
172+ modifiedStatements . unshift ( lua . createVariableDeclarationStatement ( result . hoistedIdentifiers ) ) ;
173+ }
174+ return modifiedStatements ;
143175}
144176
145177function shouldHoistSymbol ( context : TransformationContext , symbolId : lua . SymbolId , scope : Scope ) : boolean {
178+ // Always hoist in top-level of switch statements
179+ if ( scope . type === ScopeType . Switch ) {
180+ return true ;
181+ }
182+
146183 const symbolInfo = getSymbolInfo ( context , symbolId ) ;
147184 if ( ! symbolInfo ) {
148185 return false ;
@@ -183,65 +220,80 @@ function hoistVariableDeclarations(
183220 context : TransformationContext ,
184221 scope : Scope ,
185222 statements : lua . Statement [ ]
186- ) : lua . Statement [ ] {
223+ ) : { unhoistedStatements : lua . Statement [ ] ; hoistedIdentifiers : lua . Identifier [ ] } {
187224 if ( ! scope . variableDeclarations ) {
188- return statements ;
225+ return { unhoistedStatements : statements , hoistedIdentifiers : [ ] } ;
189226 }
190227
191- const result = [ ...statements ] ;
192- const hoistedLocals : lua . Identifier [ ] = [ ] ;
228+ const unhoistedStatements = [ ...statements ] ;
229+ const hoistedIdentifiers : lua . Identifier [ ] = [ ] ;
193230 for ( const declaration of scope . variableDeclarations ) {
194231 const symbols = declaration . left . map ( i => i . symbolId ) . filter ( isNonNull ) ;
195232 if ( symbols . some ( s => shouldHoistSymbol ( context , s , scope ) ) ) {
196- const index = result . indexOf ( declaration ) ;
197- assert ( index > - 1 ) ;
233+ const index = unhoistedStatements . indexOf ( declaration ) ;
234+ if ( index < 0 ) {
235+ continue ; // statements array may not contain all statements in the scope (switch-case)
236+ }
198237
199238 if ( declaration . right ) {
200239 const assignment = lua . createAssignmentStatement ( declaration . left , declaration . right ) ;
201240 lua . setNodePosition ( assignment , declaration ) ; // Preserve position info for sourcemap
202- result . splice ( index , 1 , assignment ) ;
241+ unhoistedStatements . splice ( index , 1 , assignment ) ;
203242 } else {
204- result . splice ( index , 1 ) ;
243+ unhoistedStatements . splice ( index , 1 ) ;
205244 }
206245
207- hoistedLocals . push ( ...declaration . left ) ;
208- } else if ( scope . type === ScopeType . Switch ) {
209- assert ( ! declaration . right ) ;
210- hoistedLocals . push ( ...declaration . left ) ;
246+ hoistedIdentifiers . push ( ...declaration . left ) ;
211247 }
212248 }
213249
214- if ( hoistedLocals . length > 0 ) {
215- result . unshift ( lua . createVariableDeclarationStatement ( hoistedLocals ) ) ;
216- }
217-
218- return result ;
250+ return { unhoistedStatements, hoistedIdentifiers } ;
219251}
220252
221253function hoistFunctionDefinitions (
222254 context : TransformationContext ,
223255 scope : Scope ,
224256 statements : lua . Statement [ ]
225- ) : lua . Statement [ ] {
257+ ) : { unhoistedStatements : lua . Statement [ ] ; hoistedStatements : lua . Statement [ ] ; hoistedIdentifiers : lua . Identifier [ ] } {
226258 if ( ! scope . functionDefinitions ) {
227- return statements ;
259+ return { unhoistedStatements : statements , hoistedStatements : [ ] , hoistedIdentifiers : [ ] } ;
228260 }
229261
230- const result = [ ...statements ] ;
231- const hoistedFunctions : Array < lua . VariableDeclarationStatement | lua . AssignmentStatement > = [ ] ;
262+ const unhoistedStatements = [ ...statements ] ;
263+ const hoistedStatements : lua . Statement [ ] = [ ] ;
264+ const hoistedIdentifiers : lua . Identifier [ ] = [ ] ;
232265 for ( const [ functionSymbolId , functionDefinition ] of scope . functionDefinitions ) {
233266 assert ( functionDefinition . definition ) ;
234267
235268 if ( shouldHoistSymbol ( context , functionSymbolId , scope ) ) {
236- const index = result . indexOf ( functionDefinition . definition ) ;
237- result . splice ( index , 1 ) ;
238- hoistedFunctions . push ( functionDefinition . definition ) ;
269+ const index = unhoistedStatements . indexOf ( functionDefinition . definition ) ;
270+ if ( index < 0 ) {
271+ continue ; // statements array may not contain all statements in the scope (switch-case)
272+ }
273+ unhoistedStatements . splice ( index , 1 ) ;
274+
275+ if ( lua . isVariableDeclarationStatement ( functionDefinition . definition ) ) {
276+ // Separate function definition and variable declaration
277+ assert ( functionDefinition . definition . right ) ;
278+ hoistedIdentifiers . push ( ...functionDefinition . definition . left ) ;
279+ hoistedStatements . push (
280+ lua . createAssignmentStatement (
281+ functionDefinition . definition . left ,
282+ functionDefinition . definition . right
283+ )
284+ ) ;
285+ } else {
286+ hoistedStatements . push ( functionDefinition . definition ) ;
287+ }
239288 }
240289 }
241290
242- return [ ... hoistedFunctions , ... result ] ;
291+ return { unhoistedStatements , hoistedStatements , hoistedIdentifiers } ;
243292}
244293
245- function hoistImportStatements ( scope : Scope , statements : lua . Statement [ ] ) : lua . Statement [ ] {
246- return scope . importStatements ? [ ...scope . importStatements , ...statements ] : statements ;
294+ function hoistImportStatements (
295+ scope : Scope ,
296+ statements : lua . Statement [ ]
297+ ) : { unhoistedStatements : lua . Statement [ ] ; hoistedStatements : lua . Statement [ ] } {
298+ return { unhoistedStatements : statements , hoistedStatements : scope . importStatements ?? [ ] } ;
247299}
0 commit comments