@@ -14,18 +14,30 @@ import {
1414 ScopeId ,
1515 ReactiveScopeDependency ,
1616 Place ,
17+ ReactiveScope ,
1718 ReactiveScopeDependencies ,
19+ Terminal ,
1820 isUseRefType ,
1921 isSetStateType ,
2022 isFireFunctionType ,
23+ makeScopeId ,
2124} from '../HIR' ;
25+ import { collectHoistablePropertyLoadsInInnerFn } from '../HIR/CollectHoistablePropertyLoads' ;
26+ import { collectOptionalChainSidemap } from '../HIR/CollectOptionalChainDependencies' ;
27+ import { ReactiveScopeDependencyTreeHIR } from '../HIR/DeriveMinimalDependenciesHIR' ;
2228import { DEFAULT_EXPORT } from '../HIR/Environment' ;
2329import {
2430 createTemporaryPlace ,
2531 fixScopeAndIdentifierRanges ,
2632 markInstructionIds ,
2733} from '../HIR/HIRBuilder' ;
34+ import {
35+ collectTemporariesSidemap ,
36+ DependencyCollectionContext ,
37+ handleInstruction ,
38+ } from '../HIR/PropagateScopeDependenciesHIR' ;
2839import { eachInstructionOperand , eachTerminalOperand } from '../HIR/visitors' ;
40+ import { empty } from '../Utils/Stack' ;
2941import { getOrInsertWith } from '../Utils/utils' ;
3042
3143/**
@@ -54,10 +66,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
5466 const autodepFnLoads = new Map < IdentifierId , number > ( ) ;
5567 const autodepModuleLoads = new Map < IdentifierId , Map < string , number > > ( ) ;
5668
57- const scopeInfos = new Map <
58- ScopeId ,
59- { pruned : boolean ; deps : ReactiveScopeDependencies ; hasSingleInstr : boolean }
60- > ( ) ;
69+ const scopeInfos = new Map < ScopeId , ReactiveScopeDependencies > ( ) ;
6170
6271 const loadGlobals = new Set < IdentifierId > ( ) ;
6372
@@ -71,19 +80,18 @@ export function inferEffectDependencies(fn: HIRFunction): void {
7180 const reactiveIds = inferReactiveIdentifiers ( fn ) ;
7281
7382 for ( const [ , block ] of fn . body . blocks ) {
74- if (
75- block . terminal . kind === 'scope' ||
76- block . terminal . kind === 'pruned-scope'
77- ) {
83+ if ( block . terminal . kind === 'scope' ) {
7884 const scopeBlock = fn . body . blocks . get ( block . terminal . block ) ! ;
79- scopeInfos . set ( block . terminal . scope . id , {
80- pruned : block . terminal . kind === 'pruned-scope' ,
81- deps : block . terminal . scope . dependencies ,
82- hasSingleInstr :
83- scopeBlock . instructions . length === 1 &&
84- scopeBlock . terminal . kind === 'goto' &&
85- scopeBlock . terminal . block === block . terminal . fallthrough ,
86- } ) ;
85+ if (
86+ scopeBlock . instructions . length === 1 &&
87+ scopeBlock . terminal . kind === 'goto' &&
88+ scopeBlock . terminal . block === block . terminal . fallthrough
89+ ) {
90+ scopeInfos . set (
91+ block . terminal . scope . id ,
92+ block . terminal . scope . dependencies ,
93+ ) ;
94+ }
8795 }
8896 const rewriteInstrs = new Map < InstructionId , Array < Instruction > > ( ) ;
8997 for ( const instr of block . instructions ) {
@@ -165,30 +173,21 @@ export function inferEffectDependencies(fn: HIRFunction): void {
165173 fnExpr . lvalue . identifier . scope != null
166174 ? scopeInfos . get ( fnExpr . lvalue . identifier . scope . id )
167175 : null ;
168- CompilerError . invariant ( scopeInfo != null , {
169- reason : 'Expected function expression scope to exist' ,
170- loc : value . loc ,
171- } ) ;
172- if ( scopeInfo . pruned || ! scopeInfo . hasSingleInstr ) {
173- /**
174- * TODO: retry pipeline that ensures effect function expressions
175- * are placed into their own scope
176- */
177- CompilerError . throwTodo ( {
178- reason :
179- '[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction' ,
180- loc : fnExpr . loc ,
181- } ) ;
176+ let minimalDeps : Set < ReactiveScopeDependency > ;
177+ if ( scopeInfo != null ) {
178+ minimalDeps = new Set ( scopeInfo ) ;
179+ } else {
180+ minimalDeps = inferMinimalDependencies ( fnExpr ) ;
182181 }
183-
184182 /**
185183 * Step 1: push dependencies to the effect deps array
186184 *
187185 * Note that it's invalid to prune all non-reactive deps in this pass, see
188186 * the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
189187 * explanation.
190188 */
191- for ( const dep of scopeInfo . deps ) {
189+
190+ for ( const dep of minimalDeps ) {
192191 if (
193192 ( ( isUseRefType ( dep . identifier ) ||
194193 isSetStateType ( dep . identifier ) ) &&
@@ -340,3 +339,132 @@ function inferReactiveIdentifiers(fn: HIRFunction): Set<IdentifierId> {
340339 }
341340 return reactiveIds ;
342341}
342+
343+ function inferMinimalDependencies (
344+ fnInstr : TInstruction < FunctionExpression > ,
345+ ) : Set < ReactiveScopeDependency > {
346+ const fn = fnInstr . value . loweredFunc . func ;
347+
348+ const temporaries = collectTemporariesSidemap ( fn , new Set ( ) ) ;
349+ const {
350+ hoistableObjects,
351+ processedInstrsInOptional,
352+ temporariesReadInOptional,
353+ } = collectOptionalChainSidemap ( fn ) ;
354+
355+ const hoistablePropertyLoads = collectHoistablePropertyLoadsInInnerFn (
356+ fnInstr ,
357+ temporaries ,
358+ hoistableObjects ,
359+ ) ;
360+ const hoistableToFnEntry = hoistablePropertyLoads . get ( fn . body . entry ) ;
361+ CompilerError . invariant ( hoistableToFnEntry != null , {
362+ reason :
363+ '[InferEffectDependencies] Internal invariant broken: missing entry block' ,
364+ loc : fnInstr . loc ,
365+ } ) ;
366+
367+ const dependencies = inferDependencies (
368+ fnInstr ,
369+ new Map ( [ ...temporaries , ...temporariesReadInOptional ] ) ,
370+ processedInstrsInOptional ,
371+ ) ;
372+
373+ const tree = new ReactiveScopeDependencyTreeHIR (
374+ [ ...hoistableToFnEntry . assumedNonNullObjects ] . map ( o => o . fullPath ) ,
375+ ) ;
376+ for ( const dep of dependencies ) {
377+ tree . addDependency ( { ...dep } ) ;
378+ }
379+
380+ return tree . deriveMinimalDependencies ( ) ;
381+ }
382+
383+ function inferDependencies (
384+ fnInstr : TInstruction < FunctionExpression > ,
385+ temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
386+ processedInstrsInOptional : ReadonlySet < Instruction | Terminal > ,
387+ ) : Set < ReactiveScopeDependency > {
388+ const fn = fnInstr . value . loweredFunc . func ;
389+ const context = new DependencyCollectionContext (
390+ new Set ( ) ,
391+ temporaries ,
392+ processedInstrsInOptional ,
393+ ) ;
394+ for ( const dep of fn . context ) {
395+ context . declare ( dep . identifier , {
396+ id : makeInstructionId ( 0 ) ,
397+ scope : empty ( ) ,
398+ } ) ;
399+ }
400+ const placeholderScope : ReactiveScope = {
401+ id : makeScopeId ( 0 ) ,
402+ range : {
403+ start : fnInstr . id ,
404+ end : makeInstructionId ( fnInstr . id + 1 ) ,
405+ } ,
406+ dependencies : new Set ( ) ,
407+ reassignments : new Set ( ) ,
408+ declarations : new Map ( ) ,
409+ earlyReturnValue : null ,
410+ merged : new Set ( ) ,
411+ loc : GeneratedSource ,
412+ } ;
413+ context . enterScope ( placeholderScope ) ;
414+ inferDependenciesInFn ( fn , context , temporaries ) ;
415+ context . exitScope ( placeholderScope , false ) ;
416+ const resultUnfiltered = context . deps . get ( placeholderScope ) ;
417+ CompilerError . invariant ( resultUnfiltered != null , {
418+ reason :
419+ '[InferEffectDependencies] Internal invariant broken: missing scope dependencies' ,
420+ loc : fn . loc ,
421+ } ) ;
422+
423+ const fnContext = new Set ( fn . context . map ( dep => dep . identifier . id ) ) ;
424+ const result = new Set < ReactiveScopeDependency > ( ) ;
425+ for ( const dep of resultUnfiltered ) {
426+ if ( fnContext . has ( dep . identifier . id ) ) {
427+ result . add ( dep ) ;
428+ }
429+ }
430+
431+ return result ;
432+ }
433+
434+ function inferDependenciesInFn (
435+ fn : HIRFunction ,
436+ context : DependencyCollectionContext ,
437+ temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
438+ ) : void {
439+ for ( const [ , block ] of fn . body . blocks ) {
440+ // Record referenced optional chains in phis
441+ for ( const phi of block . phis ) {
442+ for ( const operand of phi . operands ) {
443+ const maybeOptionalChain = temporaries . get ( operand [ 1 ] . identifier . id ) ;
444+ if ( maybeOptionalChain ) {
445+ context . visitDependency ( maybeOptionalChain ) ;
446+ }
447+ }
448+ }
449+ for ( const instr of block . instructions ) {
450+ if (
451+ instr . value . kind === 'FunctionExpression' ||
452+ instr . value . kind === 'ObjectMethod'
453+ ) {
454+ context . declare ( instr . lvalue . identifier , {
455+ id : instr . id ,
456+ scope : context . currentScope ,
457+ } ) ;
458+ /**
459+ * Recursively visit the inner function to extract dependencies
460+ */
461+ const innerFn = instr . value . loweredFunc . func ;
462+ context . enterInnerFn ( instr as TInstruction < FunctionExpression > , ( ) => {
463+ inferDependenciesInFn ( innerFn , context , temporaries ) ;
464+ } ) ;
465+ } else {
466+ handleInstruction ( instr , context ) ;
467+ }
468+ }
469+ }
470+ }
0 commit comments