@samitouri / QOS-React / commits / 92afc1a50b

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) {}