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