@@ -14,18 +14,30 @@ import {
14
14
ScopeId ,
15
15
ReactiveScopeDependency ,
16
16
Place ,
17
+ ReactiveScope ,
17
18
ReactiveScopeDependencies ,
19
+ Terminal ,
18
20
isUseRefType ,
19
21
isSetStateType ,
20
22
isFireFunctionType ,
23
+ makeScopeId ,
21
24
} from '../HIR' ;
25
+ import { collectHoistablePropertyLoadsInInnerFn } from '../HIR/CollectHoistablePropertyLoads' ;
26
+ import { collectOptionalChainSidemap } from '../HIR/CollectOptionalChainDependencies' ;
27
+ import { ReactiveScopeDependencyTreeHIR } from '../HIR/DeriveMinimalDependenciesHIR' ;
22
28
import { DEFAULT_EXPORT } from '../HIR/Environment' ;
23
29
import {
24
30
createTemporaryPlace ,
25
31
fixScopeAndIdentifierRanges ,
26
32
markInstructionIds ,
27
33
} from '../HIR/HIRBuilder' ;
34
+ import {
35
+ collectTemporariesSidemap ,
36
+ DependencyCollectionContext ,
37
+ handleInstruction ,
38
+ } from '../HIR/PropagateScopeDependenciesHIR' ;
28
39
import { eachInstructionOperand , eachTerminalOperand } from '../HIR/visitors' ;
40
+ import { empty } from '../Utils/Stack' ;
29
41
import { getOrInsertWith } from '../Utils/utils' ;
30
42
31
43
/**
@@ -54,10 +66,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
54
66
const autodepFnLoads = new Map < IdentifierId , number > ( ) ;
55
67
const autodepModuleLoads = new Map < IdentifierId , Map < string , number > > ( ) ;
56
68
57
- const scopeInfos = new Map <
58
- ScopeId ,
59
- { pruned : boolean ; deps : ReactiveScopeDependencies ; hasSingleInstr : boolean }
60
- > ( ) ;
69
+ const scopeInfos = new Map < ScopeId , ReactiveScopeDependencies > ( ) ;
61
70
62
71
const loadGlobals = new Set < IdentifierId > ( ) ;
63
72
@@ -71,19 +80,18 @@ export function inferEffectDependencies(fn: HIRFunction): void {
71
80
const reactiveIds = inferReactiveIdentifiers ( fn ) ;
72
81
73
82
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' ) {
78
84
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
+ }
87
95
}
88
96
const rewriteInstrs = new Map < InstructionId , Array < Instruction > > ( ) ;
89
97
for ( const instr of block . instructions ) {
@@ -165,30 +173,21 @@ export function inferEffectDependencies(fn: HIRFunction): void {
165
173
fnExpr . lvalue . identifier . scope != null
166
174
? scopeInfos . get ( fnExpr . lvalue . identifier . scope . id )
167
175
: 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 ) ;
182
181
}
183
-
184
182
/**
185
183
* Step 1: push dependencies to the effect deps array
186
184
*
187
185
* Note that it's invalid to prune all non-reactive deps in this pass, see
188
186
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
189
187
* explanation.
190
188
*/
191
- for ( const dep of scopeInfo . deps ) {
189
+
190
+ for ( const dep of minimalDeps ) {
192
191
if (
193
192
( ( isUseRefType ( dep . identifier ) ||
194
193
isSetStateType ( dep . identifier ) ) &&
@@ -340,3 +339,132 @@ function inferReactiveIdentifiers(fn: HIRFunction): Set<IdentifierId> {
340
339
}
341
340
return reactiveIds ;
342
341
}
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