22
ScopeId,
23
ReactiveScopeDependency,
24
Place,
25
+ ReactiveScope,
26
ReactiveScopeDependencies,
27
+ Terminal,
28
isUseRefType,
29
isSetStateType,
30
isFireFunctionType,
31
+ makeScopeId,
32
} from '../HIR';
33
+import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
34
+import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
35
+import {ReactiveScopeDependencyTreeHIR} from '../HIR/DeriveMinimalDependenciesHIR';
36
import {DEFAULT_EXPORT} from '../HIR/Environment';
37
import {
38
createTemporaryPlace,
39
fixScopeAndIdentifierRanges,
40
markInstructionIds,
41
} from '../HIR/HIRBuilder';
42
+import {
43
+ collectTemporariesSidemap,
44
+ DependencyCollectionContext,
45
+ handleInstruction,
46
+} from '../HIR/PropagateScopeDependenciesHIR';
47
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
48
+import {empty} from '../Utils/Stack';
49
import {getOrInsertWith} from '../Utils/utils';
50
51
/**
74
const autodepFnLoads = new Map<IdentifierId, number>();
75
const autodepModuleLoads = new Map<IdentifierId, Map<string, number>>();
76
65
- const scopeInfos = new Map<
66
- ScopeId,
67
- {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
68
- >();
77
+ const scopeInfos = new Map<ScopeId, ReactiveScopeDependencies>();
78
79
const loadGlobals = new Set<IdentifierId>();
80
88
const reactiveIds = inferReactiveIdentifiers(fn);
89
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') {
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
+ }
103
}
104
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
105
for (const instr of block.instructions) {
181
fnExpr.lvalue.identifier.scope != null
182
? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
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);
189
}
191
-
190
/**
191
* Step 1: push dependencies to the effect deps array
192
*
194
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
195
* explanation.
196
*/
197
+
198
const usedDeps = [];
200
- for (const dep of scopeInfo.deps) {
199
+ for (const dep of minimalDeps) {
200
if (
201
((isUseRefType(dep.identifier) ||
202
isSetStateType(dep.identifier)) &&
421
422
return sourceLocations;
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
+}