@@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope(
176176 * $2 = LoadLocal 'foo'
177177 * $3 = CallExpression $2($1)
178178 * ```
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.
181183 *
182184 * If a LoadLocal or PropertyLoad instruction is within the reactive scope range
183185 * (a proxy for mutable range) of the load source, later instructions may
@@ -215,7 +217,29 @@ export function collectTemporariesSidemap(
215217 fn : HIRFunction ,
216218 usedOutsideDeclaringScope : ReadonlySet < DeclarationId > ,
217219) : 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 {
219243 for ( const [ _ , block ] of fn . body . blocks ) {
220244 for ( const instr of block . instructions ) {
221245 const { value, lvalue} = instr ;
@@ -224,27 +248,51 @@ export function collectTemporariesSidemap(
224248 ) ;
225249
226250 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+ }
234266 } else if (
235267 value . kind === 'LoadLocal' &&
236268 lvalue . identifier . name == null &&
237269 value . place . identifier . name !== null &&
238270 ! usedOutside
239271 ) {
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+ ) ;
244293 }
245294 }
246295 }
247- return temporaries ;
248296}
249297
250298function getProperty (
@@ -310,6 +358,12 @@ class Context {
310358 #temporaries: ReadonlyMap < IdentifierId , ReactiveScopeDependency > ;
311359 #temporariesUsedOutsideScope: ReadonlySet < DeclarationId > ;
312360
361+ /**
362+ * Tracks the traversal state. See Context.declare for explanation of why this
363+ * is needed.
364+ */
365+ inInnerFn : boolean = false ;
366+
313367 constructor (
314368 temporariesUsedOutsideScope : ReadonlySet < DeclarationId > ,
315369 temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
@@ -360,12 +414,23 @@ class Context {
360414 }
361415
362416 /*
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).
367431 */
368432 declare ( identifier : Identifier , decl : Decl ) : void {
433+ if ( this . inInnerFn ) return ;
369434 if ( ! this . #declarations. has ( identifier . declarationId ) ) {
370435 this . #declarations. set ( identifier . declarationId , decl ) ;
371436 }
@@ -575,7 +640,10 @@ function collectDependencies(
575640 fn : HIRFunction ,
576641 usedOutsideDeclaringScope : ReadonlySet < DeclarationId > ,
577642 temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
578- processedInstrsInOptional : ReadonlySet < InstructionId > ,
643+ processedInstrsInOptional : ReadonlyMap <
644+ HIRFunction ,
645+ ReadonlySet < InstructionId >
646+ > ,
579647) : Map < ReactiveScope , Array < ReactiveScopeDependency > > {
580648 const context = new Context ( usedOutsideDeclaringScope , temporaries ) ;
581649
@@ -595,6 +663,12 @@ function collectDependencies(
595663
596664 const scopeTraversal = new ScopeBlockTraversal ( ) ;
597665
666+ const shouldSkipInstructionDependencies = (
667+ fn : HIRFunction ,
668+ id : InstructionId ,
669+ ) : boolean => {
670+ return processedInstrsInOptional . get ( fn ) ?. has ( id ) ?? false ;
671+ } ;
598672 for ( const [ blockId , block ] of fn . body . blocks ) {
599673 scopeTraversal . recordScopes ( block ) ;
600674 const scopeBlockInfo = scopeTraversal . blockInfos . get ( blockId ) ;
@@ -614,12 +688,12 @@ function collectDependencies(
614688 }
615689 }
616690 for ( const instr of block . instructions ) {
617- if ( ! processedInstrsInOptional . has ( instr . id ) ) {
691+ if ( ! shouldSkipInstructionDependencies ( fn , instr . id ) ) {
618692 handleInstruction ( instr , context ) ;
619693 }
620694 }
621695
622- if ( ! processedInstrsInOptional . has ( block . terminal . id ) ) {
696+ if ( ! shouldSkipInstructionDependencies ( fn , block . terminal . id ) ) {
623697 for ( const place of eachTerminalOperand ( block . terminal ) ) {
624698 context . visitOperand ( place ) ;
625699 }
0 commit comments