@@ -22,18 +22,30 @@ import {
22
22
ScopeId ,
23
23
ReactiveScopeDependency ,
24
24
Place ,
25
+ ReactiveScope ,
25
26
ReactiveScopeDependencies ,
27
+ Terminal ,
26
28
isUseRefType ,
27
29
isSetStateType ,
28
30
isFireFunctionType ,
31
+ makeScopeId ,
29
32
} from '../HIR' ;
33
+ import { collectHoistablePropertyLoadsInInnerFn } from '../HIR/CollectHoistablePropertyLoads' ;
34
+ import { collectOptionalChainSidemap } from '../HIR/CollectOptionalChainDependencies' ;
35
+ import { ReactiveScopeDependencyTreeHIR } from '../HIR/DeriveMinimalDependenciesHIR' ;
30
36
import { DEFAULT_EXPORT } from '../HIR/Environment' ;
31
37
import {
32
38
createTemporaryPlace ,
33
39
fixScopeAndIdentifierRanges ,
34
40
markInstructionIds ,
35
41
} from '../HIR/HIRBuilder' ;
42
+ import {
43
+ collectTemporariesSidemap ,
44
+ DependencyCollectionContext ,
45
+ handleInstruction ,
46
+ } from '../HIR/PropagateScopeDependenciesHIR' ;
36
47
import { eachInstructionOperand , eachTerminalOperand } from '../HIR/visitors' ;
48
+ import { empty } from '../Utils/Stack' ;
37
49
import { getOrInsertWith } from '../Utils/utils' ;
38
50
39
51
/**
@@ -62,10 +74,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
62
74
const autodepFnLoads = new Map < IdentifierId , number > ( ) ;
63
75
const autodepModuleLoads = new Map < IdentifierId , Map < string , number > > ( ) ;
64
76
65
- const scopeInfos = new Map <
66
- ScopeId ,
67
- { pruned : boolean ; deps : ReactiveScopeDependencies ; hasSingleInstr : boolean }
68
- > ( ) ;
77
+ const scopeInfos = new Map < ScopeId , ReactiveScopeDependencies > ( ) ;
69
78
70
79
const loadGlobals = new Set < IdentifierId > ( ) ;
71
80
@@ -79,19 +88,18 @@ export function inferEffectDependencies(fn: HIRFunction): void {
79
88
const reactiveIds = inferReactiveIdentifiers ( fn ) ;
80
89
81
90
for ( const [ , block ] of fn . body . blocks ) {
82
- if (
83
- block . terminal . kind === 'scope' ||
84
- block . terminal . kind === 'pruned-scope'
85
- ) {
91
+ if ( block . terminal . kind === 'scope' ) {
86
92
const scopeBlock = fn . body . blocks . get ( block . terminal . block ) ! ;
87
- scopeInfos . set ( block . terminal . scope . id , {
88
- pruned : block . terminal . kind === 'pruned-scope' ,
89
- deps : block . terminal . scope . dependencies ,
90
- hasSingleInstr :
91
- scopeBlock . instructions . length === 1 &&
92
- scopeBlock . terminal . kind === 'goto' &&
93
- scopeBlock . terminal . block === block . terminal . fallthrough ,
94
- } ) ;
93
+ if (
94
+ scopeBlock . instructions . length === 1 &&
95
+ scopeBlock . terminal . kind === 'goto' &&
96
+ scopeBlock . terminal . block === block . terminal . fallthrough
97
+ ) {
98
+ scopeInfos . set (
99
+ block . terminal . scope . id ,
100
+ block . terminal . scope . dependencies ,
101
+ ) ;
102
+ }
95
103
}
96
104
const rewriteInstrs = new Map < InstructionId , Array < Instruction > > ( ) ;
97
105
for ( const instr of block . instructions ) {
@@ -173,31 +181,22 @@ export function inferEffectDependencies(fn: HIRFunction): void {
173
181
fnExpr . lvalue . identifier . scope != null
174
182
? scopeInfos . get ( fnExpr . lvalue . identifier . scope . id )
175
183
: null ;
176
- CompilerError . invariant ( scopeInfo != null , {
177
- reason : 'Expected function expression scope to exist' ,
178
- loc : value . loc ,
179
- } ) ;
180
- if ( scopeInfo . pruned || ! scopeInfo . hasSingleInstr ) {
181
- /**
182
- * TODO: retry pipeline that ensures effect function expressions
183
- * are placed into their own scope
184
- */
185
- CompilerError . throwTodo ( {
186
- reason :
187
- '[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction' ,
188
- loc : fnExpr . loc ,
189
- } ) ;
184
+ let minimalDeps : Set < ReactiveScopeDependency > ;
185
+ if ( scopeInfo != null ) {
186
+ minimalDeps = new Set ( scopeInfo ) ;
187
+ } else {
188
+ minimalDeps = inferMinimalDependencies ( fnExpr ) ;
190
189
}
191
-
192
190
/**
193
191
* Step 1: push dependencies to the effect deps array
194
192
*
195
193
* Note that it's invalid to prune all non-reactive deps in this pass, see
196
194
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
197
195
* explanation.
198
196
*/
197
+
199
198
const usedDeps = [ ] ;
200
- for ( const dep of scopeInfo . deps ) {
199
+ for ( const dep of minimalDeps ) {
201
200
if (
202
201
( ( isUseRefType ( dep . identifier ) ||
203
202
isSetStateType ( dep . identifier ) ) &&
@@ -422,3 +421,132 @@ function collectDepUsages(
422
421
423
422
return sourceLocations ;
424
423
}
424
+
425
+ function inferMinimalDependencies (
426
+ fnInstr : TInstruction < FunctionExpression > ,
427
+ ) : Set < ReactiveScopeDependency > {
428
+ const fn = fnInstr . value . loweredFunc . func ;
429
+
430
+ const temporaries = collectTemporariesSidemap ( fn , new Set ( ) ) ;
431
+ const {
432
+ hoistableObjects,
433
+ processedInstrsInOptional,
434
+ temporariesReadInOptional,
435
+ } = collectOptionalChainSidemap ( fn ) ;
436
+
437
+ const hoistablePropertyLoads = collectHoistablePropertyLoadsInInnerFn (
438
+ fnInstr ,
439
+ temporaries ,
440
+ hoistableObjects ,
441
+ ) ;
442
+ const hoistableToFnEntry = hoistablePropertyLoads . get ( fn . body . entry ) ;
443
+ CompilerError . invariant ( hoistableToFnEntry != null , {
444
+ reason :
445
+ '[InferEffectDependencies] Internal invariant broken: missing entry block' ,
446
+ loc : fnInstr . loc ,
447
+ } ) ;
448
+
449
+ const dependencies = inferDependencies (
450
+ fnInstr ,
451
+ new Map ( [ ...temporaries , ...temporariesReadInOptional ] ) ,
452
+ processedInstrsInOptional ,
453
+ ) ;
454
+
455
+ const tree = new ReactiveScopeDependencyTreeHIR (
456
+ [ ...hoistableToFnEntry . assumedNonNullObjects ] . map ( o => o . fullPath ) ,
457
+ ) ;
458
+ for ( const dep of dependencies ) {
459
+ tree . addDependency ( { ...dep } ) ;
460
+ }
461
+
462
+ return tree . deriveMinimalDependencies ( ) ;
463
+ }
464
+
465
+ function inferDependencies (
466
+ fnInstr : TInstruction < FunctionExpression > ,
467
+ temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
468
+ processedInstrsInOptional : ReadonlySet < Instruction | Terminal > ,
469
+ ) : Set < ReactiveScopeDependency > {
470
+ const fn = fnInstr . value . loweredFunc . func ;
471
+ const context = new DependencyCollectionContext (
472
+ new Set ( ) ,
473
+ temporaries ,
474
+ processedInstrsInOptional ,
475
+ ) ;
476
+ for ( const dep of fn . context ) {
477
+ context . declare ( dep . identifier , {
478
+ id : makeInstructionId ( 0 ) ,
479
+ scope : empty ( ) ,
480
+ } ) ;
481
+ }
482
+ const placeholderScope : ReactiveScope = {
483
+ id : makeScopeId ( 0 ) ,
484
+ range : {
485
+ start : fnInstr . id ,
486
+ end : makeInstructionId ( fnInstr . id + 1 ) ,
487
+ } ,
488
+ dependencies : new Set ( ) ,
489
+ reassignments : new Set ( ) ,
490
+ declarations : new Map ( ) ,
491
+ earlyReturnValue : null ,
492
+ merged : new Set ( ) ,
493
+ loc : GeneratedSource ,
494
+ } ;
495
+ context . enterScope ( placeholderScope ) ;
496
+ inferDependenciesInFn ( fn , context , temporaries ) ;
497
+ context . exitScope ( placeholderScope , false ) ;
498
+ const resultUnfiltered = context . deps . get ( placeholderScope ) ;
499
+ CompilerError . invariant ( resultUnfiltered != null , {
500
+ reason :
501
+ '[InferEffectDependencies] Internal invariant broken: missing scope dependencies' ,
502
+ loc : fn . loc ,
503
+ } ) ;
504
+
505
+ const fnContext = new Set ( fn . context . map ( dep => dep . identifier . id ) ) ;
506
+ const result = new Set < ReactiveScopeDependency > ( ) ;
507
+ for ( const dep of resultUnfiltered ) {
508
+ if ( fnContext . has ( dep . identifier . id ) ) {
509
+ result . add ( dep ) ;
510
+ }
511
+ }
512
+
513
+ return result ;
514
+ }
515
+
516
+ function inferDependenciesInFn (
517
+ fn : HIRFunction ,
518
+ context : DependencyCollectionContext ,
519
+ temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
520
+ ) : void {
521
+ for ( const [ , block ] of fn . body . blocks ) {
522
+ // Record referenced optional chains in phis
523
+ for ( const phi of block . phis ) {
524
+ for ( const operand of phi . operands ) {
525
+ const maybeOptionalChain = temporaries . get ( operand [ 1 ] . identifier . id ) ;
526
+ if ( maybeOptionalChain ) {
527
+ context . visitDependency ( maybeOptionalChain ) ;
528
+ }
529
+ }
530
+ }
531
+ for ( const instr of block . instructions ) {
532
+ if (
533
+ instr . value . kind === 'FunctionExpression' ||
534
+ instr . value . kind === 'ObjectMethod'
535
+ ) {
536
+ context . declare ( instr . lvalue . identifier , {
537
+ id : instr . id ,
538
+ scope : context . currentScope ,
539
+ } ) ;
540
+ /**
541
+ * Recursively visit the inner function to extract dependencies
542
+ */
543
+ const innerFn = instr . value . loweredFunc . func ;
544
+ context . enterInnerFn ( instr as TInstruction < FunctionExpression > , ( ) => {
545
+ inferDependenciesInFn ( innerFn , context , temporaries ) ;
546
+ } ) ;
547
+ } else {
548
+ handleInstruction ( instr , context ) ;
549
+ }
550
+ }
551
+ }
552
+ }
0 commit comments