77 Set_union ,
88 getOrInsertDefault ,
99} from '../Utils/utils' ;
10+ import { collectOptionalChainSidemap } from './CollectOptionalChainDependencies' ;
1011import {
1112 BasicBlock ,
1213 BlockId ,
@@ -15,10 +16,13 @@ import {
1516 HIRFunction ,
1617 Identifier ,
1718 IdentifierId ,
19+ Instruction ,
20+ InstructionId ,
1821 InstructionValue ,
1922 ReactiveScopeDependency ,
2023 ScopeId ,
2124} from './HIR' ;
25+ import { collectTemporariesSidemap } from './PropagateScopeDependenciesHIR' ;
2226
2327/**
2428 * Helper function for `PropagateScopeDependencies`.
@@ -73,30 +77,73 @@ export function collectHoistablePropertyLoads(
7377 fn : HIRFunction ,
7478 temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
7579 optionals : ReadonlyMap < BlockId , ReactiveScopeDependency > ,
76- ) : ReadonlyMap < ScopeId , BlockInfo > {
80+ nestedFnContext : NestedFunctionContext | null ,
81+ ) : ReadonlyMap < BlockId , BlockInfo > {
7782 const registry = new PropertyPathRegistry ( ) ;
7883
79- const nodes = collectNonNullsInBlocks ( fn , temporaries , optionals , registry ) ;
84+ const functionExpressionLoads = collectFunctionExpressionFakeLoads ( fn ) ;
85+ const actuallyEvaluatedTemporaries = new Map (
86+ [ ...temporaries ] . filter ( ( [ id ] ) => ! functionExpressionLoads . has ( id ) ) ,
87+ ) ;
88+
89+ /**
90+ * Due to current limitations of mutable range inference, there are edge cases in
91+ * which we infer known-immutable values (e.g. props or hook params) to have a
92+ * mutable range and scope.
93+ * (see `destructure-array-declaration-to-context-var` fixture)
94+ * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
95+ * is being rewritten to HIR).
96+ */
97+ const knownImmutableIdentifiers = new Set < IdentifierId > ( ) ;
98+ if ( fn . fnType === 'Component' || fn . fnType === 'Hook' ) {
99+ for ( const p of fn . params ) {
100+ if ( p . kind === 'Identifier' ) {
101+ knownImmutableIdentifiers . add ( p . identifier . id ) ;
102+ }
103+ }
104+ }
105+ const nodes = collectNonNullsInBlocks ( fn , {
106+ temporaries : actuallyEvaluatedTemporaries ,
107+ knownImmutableIdentifiers,
108+ optionals,
109+ registry,
110+ nestedFnContext,
111+ } ) ;
80112 propagateNonNull ( fn , nodes , registry ) ;
81113
82- const nodesKeyedByScopeId = new Map < ScopeId , BlockInfo > ( ) ;
114+ return nodes ;
115+ }
116+
117+ export function keyByScopeId < T > (
118+ fn : HIRFunction ,
119+ source : ReadonlyMap < BlockId , T > ,
120+ ) : ReadonlyMap < ScopeId , T > {
121+ const keyedByScopeId = new Map < ScopeId , T > ( ) ;
83122 for ( const [ _ , block ] of fn . body . blocks ) {
84123 if ( block . terminal . kind === 'scope' ) {
85- nodesKeyedByScopeId . set (
124+ keyedByScopeId . set (
86125 block . terminal . scope . id ,
87- nodes . get ( block . terminal . block ) ! ,
126+ source . get ( block . terminal . block ) ! ,
88127 ) ;
89128 }
90129 }
91-
92- return nodesKeyedByScopeId ;
130+ return keyedByScopeId ;
93131}
94132
95133export type BlockInfo = {
96134 block : BasicBlock ;
97135 assumedNonNullObjects : ReadonlySet < PropertyPathNode > ;
98136} ;
99137
138+ type NestedFunctionContext = {
139+ /**
140+ * Instruction id of the outermost nested function declaration.
141+ */
142+ outermostFnInstrId : InstructionId ;
143+ // Captured from outermost scope
144+ capturedIdentifiers : Set < IdentifierId > ;
145+ } ;
146+
100147/**
101148 * PropertyLoadRegistry data structure to dedupe property loads (e.g. a.b.c)
102149 * and make computing sets intersections simpler.
@@ -196,45 +243,33 @@ class PropertyPathRegistry {
196243
197244function getMaybeNonNullInInstruction (
198245 instr : InstructionValue ,
199- temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
200- registry : PropertyPathRegistry ,
246+ context : CollectNonNullsInBlocksContext ,
201247) : PropertyPathNode | null {
202248 let path = null ;
203249 if ( instr . kind === 'PropertyLoad' ) {
204- path = temporaries . get ( instr . object . identifier . id ) ?? {
250+ path = context . temporaries . get ( instr . object . identifier . id ) ?? {
205251 identifier : instr . object . identifier ,
206252 path : [ ] ,
207253 } ;
208254 } else if ( instr . kind === 'Destructure' ) {
209- path = temporaries . get ( instr . value . identifier . id ) ?? null ;
255+ path = context . temporaries . get ( instr . value . identifier . id ) ?? null ;
210256 } else if ( instr . kind === 'ComputedLoad' ) {
211- path = temporaries . get ( instr . object . identifier . id ) ?? null ;
257+ path = context . temporaries . get ( instr . object . identifier . id ) ?? null ;
212258 }
213- return path != null ? registry . getOrCreateProperty ( path ) : null ;
259+ return path != null ? context . registry . getOrCreateProperty ( path ) : null ;
214260}
215261
262+ type CollectNonNullsInBlocksContext = {
263+ temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ;
264+ knownImmutableIdentifiers : ReadonlySet < IdentifierId > ;
265+ optionals : ReadonlyMap < BlockId , ReactiveScopeDependency > ;
266+ registry : PropertyPathRegistry ;
267+ nestedFnContext : NestedFunctionContext | null ;
268+ } ;
216269function collectNonNullsInBlocks (
217270 fn : HIRFunction ,
218- temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
219- optionals : ReadonlyMap < BlockId , ReactiveScopeDependency > ,
220- registry : PropertyPathRegistry ,
271+ context : CollectNonNullsInBlocksContext ,
221272) : ReadonlyMap < BlockId , BlockInfo > {
222- /**
223- * Due to current limitations of mutable range inference, there are edge cases in
224- * which we infer known-immutable values (e.g. props or hook params) to have a
225- * mutable range and scope.
226- * (see `destructure-array-declaration-to-context-var` fixture)
227- * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
228- * is being rewritten to HIR).
229- */
230- const knownImmutableIdentifiers = new Set < IdentifierId > ( ) ;
231- if ( fn . fnType === 'Component' || fn . fnType === 'Hook' ) {
232- for ( const p of fn . params ) {
233- if ( p . kind === 'Identifier' ) {
234- knownImmutableIdentifiers . add ( p . identifier . id ) ;
235- }
236- }
237- }
238273 /**
239274 * Known non-null objects such as functional component props can be safely
240275 * read from any block.
@@ -246,7 +281,9 @@ function collectNonNullsInBlocks(
246281 fn . params [ 0 ] . kind === 'Identifier'
247282 ) {
248283 const identifier = fn . params [ 0 ] . identifier ;
249- knownNonNullIdentifiers . add ( registry . getOrCreateIdentifier ( identifier ) ) ;
284+ knownNonNullIdentifiers . add (
285+ context . registry . getOrCreateIdentifier ( identifier ) ,
286+ ) ;
250287 }
251288 const nodes = new Map < BlockId , BlockInfo > ( ) ;
252289 for ( const [ _ , block ] of fn . body . blocks ) {
@@ -258,20 +295,22 @@ function collectNonNullsInBlocks(
258295 block,
259296 assumedNonNullObjects,
260297 } ) ;
261- const maybeOptionalChain = optionals . get ( block . id ) ;
298+ const maybeOptionalChain = context . optionals . get ( block . id ) ;
262299 if ( maybeOptionalChain != null ) {
263300 assumedNonNullObjects . add (
264- registry . getOrCreateProperty ( maybeOptionalChain ) ,
301+ context . registry . getOrCreateProperty ( maybeOptionalChain ) ,
265302 ) ;
266303 continue ;
267304 }
268305 for ( const instr of block . instructions ) {
269- const maybeNonNull = getMaybeNonNullInInstruction (
270- instr . value ,
271- temporaries ,
272- registry ,
273- ) ;
274- if ( maybeNonNull != null ) {
306+ const maybeNonNull = getMaybeNonNullInInstruction ( instr . value , context ) ;
307+ if (
308+ maybeNonNull != null &&
309+ ( context . nestedFnContext == null ||
310+ context . nestedFnContext . capturedIdentifiers . has (
311+ maybeNonNull . fullPath . identifier . id ,
312+ ) )
313+ ) {
275314 const baseIdentifier = maybeNonNull . fullPath . identifier ;
276315 /**
277316 * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
@@ -289,17 +328,48 @@ function collectNonNullsInBlocks(
289328 baseIdentifier . scope != null &&
290329 inRange (
291330 {
292- id : instr . id ,
331+ id :
332+ context . nestedFnContext != null
333+ ? context . nestedFnContext . outermostFnInstrId
334+ : instr . id ,
293335 } ,
294336 baseIdentifier . scope . range ,
295337 ) ;
296338 if (
297339 ! isMutableAtInstr ||
298- knownImmutableIdentifiers . has ( baseIdentifier . id )
340+ context . knownImmutableIdentifiers . has ( baseIdentifier . id )
299341 ) {
300342 assumedNonNullObjects . add ( maybeNonNull ) ;
301343 }
302344 }
345+ if (
346+ instr . value . kind === 'FunctionExpression' &&
347+ ! fn . env . config . enableTreatFunctionDepsAsConditional
348+ ) {
349+ const innerFn = instr . value . loweredFunc ;
350+ const innerTemporaries = collectTemporariesSidemap (
351+ innerFn . func ,
352+ new Set ( ) ,
353+ ) ;
354+ const innerOptionals = collectOptionalChainSidemap ( innerFn . func ) ;
355+ const innerHoistableMap = collectHoistablePropertyLoads (
356+ innerFn . func ,
357+ innerTemporaries ,
358+ innerOptionals . hoistableObjects ,
359+ context . nestedFnContext ?? {
360+ outermostFnInstrId : instr . id ,
361+ capturedIdentifiers : new Set (
362+ innerFn . func . context . map ( place => place . identifier . id ) ,
363+ ) ,
364+ } ,
365+ ) ;
366+ const innerHoistables = assertNonNull (
367+ innerHoistableMap . get ( innerFn . func . body . entry ) ,
368+ ) ;
369+ for ( const entry of innerHoistables . assumedNonNullObjects ) {
370+ assumedNonNullObjects . add ( entry ) ;
371+ }
372+ }
303373 }
304374 }
305375 return nodes ;
@@ -500,3 +570,27 @@ function reduceMaybeOptionalChains(
500570 }
501571 } while ( changed ) ;
502572}
573+
574+ function collectFunctionExpressionFakeLoads (
575+ fn : HIRFunction ,
576+ ) : Set < IdentifierId > {
577+ const sources = new Map < IdentifierId , IdentifierId > ( ) ;
578+ const functionExpressionReferences = new Set < IdentifierId > ( ) ;
579+
580+ for ( const [ _ , block ] of fn . body . blocks ) {
581+ for ( const { lvalue, value} of block . instructions ) {
582+ if ( value . kind === 'FunctionExpression' ) {
583+ for ( const reference of value . loweredFunc . dependencies ) {
584+ let curr : IdentifierId | undefined = reference . identifier . id ;
585+ while ( curr != null ) {
586+ functionExpressionReferences . add ( curr ) ;
587+ curr = sources . get ( curr ) ;
588+ }
589+ }
590+ } else if ( value . kind === 'PropertyLoad' ) {
591+ sources . set ( lvalue . identifier . id , value . object . identifier . id ) ;
592+ }
593+ }
594+ }
595+ return functionExpressionReferences ;
596+ }
0 commit comments