@@ -16,6 +16,7 @@ import {
16
16
DeclarationId ,
17
17
areEqualPaths ,
18
18
IdentifierId ,
19
+ Terminal ,
19
20
} from './HIR' ;
20
21
import {
21
22
collectHoistablePropertyLoads ,
@@ -176,8 +177,10 @@ function findTemporariesUsedOutsideDeclaringScope(
176
177
* $2 = LoadLocal 'foo'
177
178
* $3 = CallExpression $2($1)
178
179
* ```
179
- * Only map LoadLocal and PropertyLoad lvalues to their source if we know that
180
- * reordering the read (from the time-of-load to time-of-use) is valid.
180
+ * @param usedOutsideDeclaringScope is used to check the correctness of
181
+ * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal /
182
+ * PropertyLoad in the returned temporaries map if reordering the read (from the
183
+ * time-of-load to time-of-use) is valid.
181
184
*
182
185
* If a LoadLocal or PropertyLoad instruction is within the reactive scope range
183
186
* (a proxy for mutable range) of the load source, later instructions may
@@ -215,7 +218,29 @@ export function collectTemporariesSidemap(
215
218
fn : HIRFunction ,
216
219
usedOutsideDeclaringScope : ReadonlySet < DeclarationId > ,
217
220
) : ReadonlyMap < IdentifierId , ReactiveScopeDependency > {
218
- const temporaries = new Map < IdentifierId , ReactiveScopeDependency > ( ) ;
221
+ const temporaries = new Map ( ) ;
222
+ collectTemporariesSidemapImpl (
223
+ fn ,
224
+ usedOutsideDeclaringScope ,
225
+ temporaries ,
226
+ false ,
227
+ ) ;
228
+ return temporaries ;
229
+ }
230
+
231
+ /**
232
+ * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a
233
+ * function and all nested functions.
234
+ *
235
+ * Note that IdentifierIds are currently unique, so we can use a single
236
+ * Map<IdentifierId, ...> across all nested functions.
237
+ */
238
+ function collectTemporariesSidemapImpl (
239
+ fn : HIRFunction ,
240
+ usedOutsideDeclaringScope : ReadonlySet < DeclarationId > ,
241
+ temporaries : Map < IdentifierId , ReactiveScopeDependency > ,
242
+ isInnerFn : boolean ,
243
+ ) : void {
219
244
for ( const [ _ , block ] of fn . body . blocks ) {
220
245
for ( const instr of block . instructions ) {
221
246
const { value, lvalue} = instr ;
@@ -224,27 +249,51 @@ export function collectTemporariesSidemap(
224
249
) ;
225
250
226
251
if ( value . kind === 'PropertyLoad' && ! usedOutside ) {
227
- const property = getProperty (
228
- value . object ,
229
- value . property ,
230
- false ,
231
- temporaries ,
232
- ) ;
233
- temporaries . set ( lvalue . identifier . id , property ) ;
252
+ if ( ! isInnerFn || temporaries . has ( value . object . identifier . id ) ) {
253
+ /**
254
+ * All dependencies of a inner / nested function must have a base
255
+ * identifier from the outermost component / hook. This is because the
256
+ * compiler cannot break an inner function into multiple granular
257
+ * scopes.
258
+ */
259
+ const property = getProperty (
260
+ value . object ,
261
+ value . property ,
262
+ false ,
263
+ temporaries ,
264
+ ) ;
265
+ temporaries . set ( lvalue . identifier . id , property ) ;
266
+ }
234
267
} else if (
235
268
value . kind === 'LoadLocal' &&
236
269
lvalue . identifier . name == null &&
237
270
value . place . identifier . name !== null &&
238
271
! usedOutside
239
272
) {
240
- temporaries . set ( lvalue . identifier . id , {
241
- identifier : value . place . identifier ,
242
- path : [ ] ,
243
- } ) ;
273
+ if (
274
+ ! isInnerFn ||
275
+ fn . context . some (
276
+ context => context . identifier . id === value . place . identifier . id ,
277
+ )
278
+ ) {
279
+ temporaries . set ( lvalue . identifier . id , {
280
+ identifier : value . place . identifier ,
281
+ path : [ ] ,
282
+ } ) ;
283
+ }
284
+ } else if (
285
+ value . kind === 'FunctionExpression' ||
286
+ value . kind === 'ObjectMethod'
287
+ ) {
288
+ collectTemporariesSidemapImpl (
289
+ value . loweredFunc . func ,
290
+ usedOutsideDeclaringScope ,
291
+ temporaries ,
292
+ true ,
293
+ ) ;
244
294
}
245
295
}
246
296
}
247
- return temporaries ;
248
297
}
249
298
250
299
function getProperty (
@@ -310,6 +359,12 @@ class Context {
310
359
#temporaries: ReadonlyMap < IdentifierId , ReactiveScopeDependency > ;
311
360
#temporariesUsedOutsideScope: ReadonlySet < DeclarationId > ;
312
361
362
+ /**
363
+ * Tracks the traversal state. See Context.declare for explanation of why this
364
+ * is needed.
365
+ */
366
+ inInnerFn : boolean = false ;
367
+
313
368
constructor (
314
369
temporariesUsedOutsideScope : ReadonlySet < DeclarationId > ,
315
370
temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
@@ -360,12 +415,23 @@ class Context {
360
415
}
361
416
362
417
/*
363
- * Records where a value was declared, and optionally, the scope where the value originated from.
364
- * This is later used to determine if a dependency should be added to a scope; if the current
365
- * scope we are visiting is the same scope where the value originates, it can't be a dependency
366
- * on itself.
418
+ * Records where a value was declared, and optionally, the scope where the
419
+ * value originated from. This is later used to determine if a dependency
420
+ * should be added to a scope; if the current scope we are visiting is the
421
+ * same scope where the value originates, it can't be a dependency on itself.
422
+ *
423
+ * Note that we do not track declarations or reassignments within inner
424
+ * functions for the following reasons:
425
+ * - inner functions cannot be split by scope boundaries and are guaranteed
426
+ * to consume their own declarations
427
+ * - reassignments within inner functions are tracked as context variables,
428
+ * which already have extended mutable ranges to account for reassignments
429
+ * - *most importantly* it's currently simply incorrect to compare inner
430
+ * function instruction ids (tracked by `decl`) with outer ones (as stored
431
+ * by root identifier mutable ranges).
367
432
*/
368
433
declare ( identifier : Identifier , decl : Decl ) : void {
434
+ if ( this . inInnerFn ) return ;
369
435
if ( ! this . #declarations. has ( identifier . declarationId ) ) {
370
436
this . #declarations. set ( identifier . declarationId , decl ) ;
371
437
}
@@ -575,7 +641,7 @@ function collectDependencies(
575
641
fn : HIRFunction ,
576
642
usedOutsideDeclaringScope : ReadonlySet < DeclarationId > ,
577
643
temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
578
- processedInstrsInOptional : ReadonlySet < InstructionId > ,
644
+ processedInstrsInOptional : ReadonlySet < Instruction | Terminal > ,
579
645
) : Map < ReactiveScope , Array < ReactiveScopeDependency > > {
580
646
const context = new Context ( usedOutsideDeclaringScope , temporaries ) ;
581
647
@@ -614,12 +680,12 @@ function collectDependencies(
614
680
}
615
681
}
616
682
for ( const instr of block . instructions ) {
617
- if ( ! processedInstrsInOptional . has ( instr . id ) ) {
683
+ if ( ! processedInstrsInOptional . has ( instr ) ) {
618
684
handleInstruction ( instr , context ) ;
619
685
}
620
686
}
621
687
622
- if ( ! processedInstrsInOptional . has ( block . terminal . id ) ) {
688
+ if ( ! processedInstrsInOptional . has ( block . terminal ) ) {
623
689
for ( const place of eachTerminalOperand ( block . terminal ) ) {
624
690
context . visitOperand ( place ) ;
625
691
}
0 commit comments