Stub for final phase of reactive scope construction w docs
Joe Savona committed
Nov 18, 2022 at 14:59 UTC
92afc1a50bb13b3e754595d3733e1b808cf55cd9
1 file changed
+99
compiler/forget/src/HIR/InferReactiveScopes.ts
new
+99
@@ -0,0 +1,99 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import { HIRFunction } from "./HIR";
9
+
10
+/**
11
+ * This is a second (final) stage of constructing reactive scopes. Prior to this pass,
12
+ * InferReactiveScopeVariables infers the sets of identifiers that "construct together",
13
+ * assigning each identifier in each scope the same ScopeId and same MutableRange which
14
+ * describes that span.
15
+ *
16
+ * Note that at this point reactive scopes describe ranges based on specific instructions
17
+ * at arbitrary points in the control flow graph. However, reactive scopes must align
18
+ * with control-flow boundaries — we can't memoize half of a loop!
19
+ *
20
+ * This pass refines the reactive scopes as follows:
21
+ *
22
+ * ## Expanding each reactive scope to align with control-flow boundaries
23
+ *
24
+ * This corresponds with the shape of the AST: a scope that extends into an if consequent
25
+ * would expand across the alternate branch, A scope that extends partway into an if
26
+ * would expand to cover the full loop body, etc.
27
+ *
28
+ * ```javascript
29
+ * function foo(cond, a) {
30
+ * ⌵ original scope
31
+ * ⌵ expanded scope
32
+ * const x = []; ⌝ ⌝
33
+ * if (cond) { ⎮ ⎮
34
+ * ... ⎮ ⎮
35
+ * x.push(a); ⌟ ⎮
36
+ * ... ⎮
37
+ * } ⌟
38
+ * }
39
+ * ```
40
+ *
41
+ * ## Merging (some) overlapping reactive scopes
42
+ *
43
+ * Two scopes overlap if there is one or more instruction that is inside the range
44
+ * of both scopes. In general, overlapping scopes are merged togther. The only
45
+ * exception to this is when one scope *shadows* another scope. For example:
46
+ *
47
+ * ```javascript
48
+ * function foo(cond, a) {
49
+ * ⌵ scope for x
50
+ * let x = []; ⌝
51
+ * if (cond) { ⎮
52
+ * ⌵ scope for y ⎮
53
+ * let y = []; ⌝ ⎮
54
+ * if (b) { ⎮ ⎮
55
+ * y.push(b); ⌟ ⎮
56
+ * } ⎮
57
+ * x.push(<div>{y}</div>); ⎮
58
+ * } ⌟
59
+ * }
60
+ * ```
61
+ *
62
+ * In this example the two scopes overlap, but mutation of the two scopes is not
63
+ * interleaved. Specifically within the y scope there are no instructions that
64
+ * modify any other scope: the inner scope "shadows" the outer one. This category
65
+ * of overlap does *NOT* merge the scopes together.
66
+ *
67
+ * The implementation is inspired by the Rust notion of "stacked borrows". We traverse
68
+ * the control-flow graph in tree form, at each point keeping track of which scopes are
69
+ * active. So initially we see
70
+ *
71
+ * `let x = []`
72
+ * active scopes: [x]
73
+ *
74
+ * and mark the x scope as active.
75
+ *
76
+ * Then we later encounter
77
+ *
78
+ * `let y = [];`
79
+ * active scopes: [x, y]
80
+ *
81
+ * Here we first check to see if 'y' is already in the list of active scopes. It isn't,
82
+ * so we push it to the stop of the stack.
83
+ *
84
+ * Then
85
+ *
86
+ * `y.push(b)`
87
+ * active scopes: [x, y]
88
+ *
89
+ * Mutates y, so we check if y is the top of the stack. It is, so no merging must occur.
90
+ *
91
+ * If instead we saw eg
92
+ *
93
+ * `x.push(b)`
94
+ * active scopes: [x, y]
95
+ *
96
+ * Then we would see that 'x' is active, but that it is shadowed. The two scopes would have
97
+ * to be merged.
98
+ */
99
+export function inferReactiveScopes(fn: HIRFunction) {}