[rhir] Promote conditional dependencies accessed by every cfg path
--- Implementation details summarized in comments. Overall, we want to calculate a `ReactiveDependencyTree` for every conditional block. If we know that conditional blocks are exhaustive (e.g. all CFG paths calculates a tree), we can take `intersection(depsFromEachBlock)` and add this to the parent Reactive + conditional scope `parentDeps = union(parentDeps, intersection(...))`. We use trees instead of individual deps here because we can still derive unconditional accesses. e.g. ``` let x = {}; // props.a is an unconditional access here if (foo(other)) { x.a = props.a.b; } else { x.b = props.a.c; } ```
Mofei Zhang committed
Mar 6, 2023 at 16:33 UTC
e68ea941e6e002411297c31f2b6e51a1795c3994
16 files changed
+753
-28
compiler/forget/src/ReactiveScopes/DeriveMinimalDependencies.ts
+97
@@ -91,6 +91,25 @@ export class ReactiveScopeDependencyTree {
91
92
return results;
93
}
94
+
95
+ promoteDepsFromExhaustiveConditionals(
96
+ trees: Array<ReactiveScopeDependencyTree>
97
+ ) {
98
+ invariant(
99
+ trees.length > 1,
100
+ "Expected trees to be at least 2 elements long."
101
+ );
102
+
103
+ for (const [id, root] of this.#roots) {
104
+ const nodesForRootId = mapNonNull(trees, (tree) => tree.#roots.get(id));
105
+ if (nodesForRootId) {
106
+ addSubtreeIntersection(
107
+ nodesForRootId.map((root) => root.properties),
108
+ root.properties
109
+ );
110
+ }
111
+ }
112
+ }
113
}
114
115
/**
@@ -251,3 +270,81 @@ function deriveMinimalDependenciesInSubtree(
270
}
271
}
272
}
273
+
274
+/**
275
+ * Adds intersection(otherProperties) to currProperties, mutating
276
+ * currProperties in place. i.e.
277
+ * currProperties = union(currProperties, intersection(otherProperties))
278
+ *
279
+ * Used to merge unconditional accesses from exhaustive conditional branches
280
+ * into the parent ReactiveDeps Tree.
281
+ * intersection(currProperties) is determined as such:
282
+ * - a node is present in the intersection iff it is present in all every
283
+ * branch
284
+ * - the type of an added node is `UnconditionalDependency` if it is a
285
+ * dependency in at least one branch (otherwise `UnconditionalAccess`)
286
+ *
287
+ * @param otherProperties (read-only) an array of node properties containing
288
+ * only unconditionally accessed nodes. Each element represents a
289
+ * subtree of reactive dependencies from a single CFG branch.
290
+ * otherProperties must represent all reachable branches.
291
+ * @param currProperties (mutable) return by argument properties of a node
292
+ *
293
+ * otherProperties and currProperties must be properties of disjoint nodes
294
+ * that represent the same dependency (identifier + path).
295
+ */
296
+function addSubtreeIntersection(
297
+ otherProperties: Array<Map<string, DependencyNode>>,
298
+ currProperties: Map<string, DependencyNode>
299
+) {
300
+ invariant(
301
+ otherProperties.length > 1,
302
+ "[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long."
303
+ );
304
+
305
+ otherProperties.forEach((properties) =>
306
+ properties.forEach((node, _) =>
307
+ invariant(
308
+ !isUnconditional(node.accessType),
309
+ "[DeriveMinimalDependencies] Expected otherProperties to only contain unconditional nodes!"
310
+ )
311
+ )
312
+ );
313
+
314
+ for (const [propertyName, currNode] of currProperties) {
315
+ const otherNodes = mapNonNull(otherProperties, (properties) =>
316
+ properties.get(propertyName)
317
+ );
318
+
319
+ // intersection(otherNodes[propertyName]) only exists if each element in
320
+ // otherProperties accesses propertyName.
321
+ if (otherNodes) {
322
+ addSubtreeIntersection(
323
+ otherNodes.map((node) => node.properties),
324
+ currNode.properties
325
+ );
326
+
327
+ const isDep = otherNodes.some((tree) => isDependency(tree.accessType));
328
+ const externalAccessType = isDep
329
+ ? PropertyAccessType.UnconditionalDependency
330
+ : PropertyAccessType.UnconditionalAccess;
331
+ currNode.accessType = merge(externalAccessType, currNode.accessType);
332
+ }
333
+ }
334
+}
335
+
336
+function mapNonNull<T extends NonNullable<V>, V, U>(
337
+ arr: Array<U>,
338
+ fn: (arg0: U) => T | undefined | null
339
+): Array<T> | null {
340
+ const result = [];
341
+ for (let i = 0; i < arr.length; i++) {
342
+ const element = fn(arr[i]);
343
+ if (element) {
344
+ result.push(element);
345
+ } else {
346
+ return null;
347
+ }
348
+ }
349
+ return result;
350
+}
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+87
-28
@@ -63,11 +63,18 @@ type Scopes = Array<ReactiveScope>;
63
class Context {
64
#declarations: DeclMap = new Map();
65
#reassignments: Map<Identifier, Decl> = new Map();
66
+ // Reactive dependencies used in the current reactive scope.
67
#dependencies: ReactiveScopeDependencyTree =
68
new ReactiveScopeDependencyTree();
69
#properties: Map<Identifier, ReactiveScopeDependencyInfo> = new Map();
70
#temporaries: Map<Identifier, Place> = new Map();
71
#inConditionalWithinScope: boolean = false;
72
+ // Reactive dependencies used unconditionally in the current conditional.
73
+ // Composed of dependencies:
74
+ // - directly accessed within block (added in visitDep)
75
+ // - accessed by all cfg branches (added through promoteDeps)
76
+ #depsInCurrentConditional: ReactiveScopeDependencyTree =
77
+ new ReactiveScopeDependencyTree();
78
#scopes: Scopes = [];
79
80
enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
@@ -105,11 +112,48 @@ class Context {
112
return minScopeDependencies;
113
}
114
108
- enterConditional(fn: () => void) {
115
+ /**
116
+ * We track and return unconditional accesses / deps within this conditional.
117
+ * If an object property is always used (i.e. in every conditional path), we
118
+ * want to promote it to an unconditional access / dependency.
119
+ *
120
+ * The caller of `enterConditional` is responsible determining for promotion.
121
+ * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results.
122
+ *
123
+ * e.g. we want to mark props.a.b as an unconditional dep here
124
+ * if (foo(...)) {
125
+ * access(props.a.b);
126
+ * } else {
127
+ * access(props.a.b);
128
+ * }
129
+ */
130
+ enterConditional(fn: () => void): ReactiveScopeDependencyTree {
131
const prevInConditional = this.#inConditionalWithinScope;
132
+ const prevUncondAccessed = this.#depsInCurrentConditional;
133
this.#inConditionalWithinScope = true;
134
+ this.#depsInCurrentConditional = new ReactiveScopeDependencyTree();
135
fn();
136
+ const result = this.#depsInCurrentConditional;
137
this.#inConditionalWithinScope = prevInConditional;
138
+ this.#depsInCurrentConditional = prevUncondAccessed;
139
+ return result;
140
+ }
141
+
142
+ /**
143
+ * Add dependencies from exhaustive CFG paths into the current ReactiveDeps
144
+ * tree. If a property is used in every CFG path, it is promoted to an
145
+ * unconditional access / dependency here.
146
+ * @param depsInConditionals
147
+ */
148
+ promoteDepsFromExhaustiveConditionals(
149
+ depsInConditionals: Array<ReactiveScopeDependencyTree>
150
+ ) {
151
+ this.#dependencies.promoteDepsFromExhaustiveConditionals(
152
+ depsInConditionals
153
+ );
154
+ this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals(
155
+ depsInConditionals
156
+ );
157
}
158
159
/**
@@ -237,6 +281,10 @@ class Context {
281
(currentDeclaration.scope == null ||
282
currentDeclaration.scope !== currentScope)
283
) {
284
+ this.#depsInCurrentConditional.add({
285
+ ...maybeDependency,
286
+ cond: true,
287
+ });
288
// Add info about this dependency to the existing tree
289
// We do not try to join/reduce dependencies here due to missing info
290
this.#dependencies.add(maybeDependency);
@@ -316,38 +364,43 @@ function visit(context: Context, block: ReactiveBlock): void {
364
}
365
case "if": {
366
context.visitOperand(terminal.test);
319
- /**
320
- * TODO: Track dependencies always accessed within consequent and ones
321
- * always accessed within alternate. If a dependency is always accessed in
322
- * both, we can promote it to an unconditional dependency.
323
- *
324
- * e.g. props.a.b is unconditionally accessed here.
325
- * if (foo(...)) {
326
- * access(props.a.b);
327
- * } else {
328
- * access(props.a.b);
329
- * }
330
- *
331
- * To deal with nested if-branches, enterConditional should return a list
332
- * of dependencies unconditionally accessed within the callback.
333
- */
334
- context.enterConditional(() => {
335
- visit(context, terminal.consequent);
336
- if (terminal.alternate !== null) {
337
- visit(context, terminal.alternate);
338
- }
367
+ const { consequent, alternate } = terminal;
368
+ const depsInIf = context.enterConditional(() => {
369
+ visit(context, consequent);
370
});
371
+ if (alternate !== null) {
372
+ const depsInElse = context.enterConditional(() => {
373
+ visit(context, alternate);
374
+ });
375
+ context.promoteDepsFromExhaustiveConditionals([
376
+ depsInIf,
377
+ depsInElse,
378
+ ]);
379
+ }
380
break;
381
}
382
case "switch": {
383
context.visitOperand(terminal.test);
344
- context.enterConditional(() => {
345
- for (const case_ of terminal.cases) {
346
- if (case_.block !== undefined) {
347
- visit(context, case_.block);
348
- }
384
+ const depsInCases = [];
385
+ let foundDefault = false;
386
+ // This can underestimate unconditional accesses due to the current
387
+ // CFG representation for fallthrough. This is safe. It only
388
+ // reduces granularity of dependencies.
389
+ for (const { test, block } of terminal.cases) {
390
+ if (test == null) {
391
+ foundDefault = true;
392
}
350
- });
393
+ if (block !== undefined) {
394
+ depsInCases.push(
395
+ context.enterConditional(() => {
396
+ visit(context, block);
397
+ })
398
+ );
399
+ }
400
+ }
401
+ if (foundDefault) {
402
+ context.promoteDepsFromExhaustiveConditionals(depsInCases);
403
+ }
404
break;
405
}
406
default: {
@@ -382,10 +435,16 @@ function visitReactiveValue(
435
case "ConditionalExpression": {
436
visitReactiveValue(context, id, value.test);
437
385
- context.enterConditional(() => {
438
+ const consequentDeps = context.enterConditional(() => {
439
visitReactiveValue(context, id, value.consequent);
440
+ });
441
+ const alternateDeps = context.enterConditional(() => {
442
visitReactiveValue(context, id, value.alternate);
443
});
444
+ context.promoteDepsFromExhaustiveConditionals([
445
+ consequentDeps,
446
+ alternateDeps,
447
+ ]);
448
break;
449
}
450
case "SequenceExpression": {
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-condexpr.expect.md
new
+41
@@ -0,0 +1,41 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should be added as a unconditional dependency to the reactive
6
+// scope that produces x, since it is accessed unconditionally in all cfg
7
+// paths
8
+
9
+function TestCondDepInConditionalExpr(props, other) {
10
+ const x = foo(other) ? bar(props.a.b) : baz(props.a.b);
11
+ return x;
12
+}
13
+
14
+```
15
+
16
+## Code
17
+
18
+```javascript
19
+// props.a.b should be added as a unconditional dependency to the reactive
20
+// scope that produces x, since it is accessed unconditionally in all cfg
21
+// paths
22
+
23
+function TestCondDepInConditionalExpr(props, other) {
24
+ const $ = React.unstable_useMemoCache(3);
25
+ const c_0 = $[0] !== other;
26
+ const c_1 = $[1] !== props.a.b;
27
+ let t0;
28
+ if (c_0 || c_1) {
29
+ t0 = foo(other) ? bar(props.a.b) : baz(props.a.b);
30
+ $[0] = other;
31
+ $[1] = props.a.b;
32
+ $[2] = t0;
33
+ } else {
34
+ t0 = $[2];
35
+ }
36
+ const x = t0;
37
+ return x;
38
+}
39
+
40
+```
41
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-condexpr.js
new
+8
@@ -0,0 +1,8 @@
1
+// props.a.b should be added as a unconditional dependency to the reactive
2
+// scope that produces x, since it is accessed unconditionally in all cfg
3
+// paths
4
+
5
+function TestCondDepInConditionalExpr(props, other) {
6
+ const x = foo(other) ? bar(props.a.b) : baz(props.a.b);
7
+ return x;
8
+}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-ifelse.expect.md
new
+59
@@ -0,0 +1,59 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should be added as a unconditional dependency to the reactive
6
+// scope that produces x, since it is accessed unconditionally in all cfg
7
+// paths
8
+
9
+function TestCondDepInDirectIfElse(props, other) {
10
+ const x = {};
11
+ if (foo(other)) {
12
+ x.b = props.a.b;
13
+ } else {
14
+ x.c = props.a.b;
15
+ }
16
+ return x;
17
+}
18
+
19
+```
20
+
21
+## Code
22
+
23
+```javascript
24
+// props.a.b should be added as a unconditional dependency to the reactive
25
+// scope that produces x, since it is accessed unconditionally in all cfg
26
+// paths
27
+
28
+function TestCondDepInDirectIfElse(props, other) {
29
+ const $ = React.unstable_useMemoCache(5);
30
+ const c_0 = $[0] !== other;
31
+ const c_1 = $[1] !== props.a.b;
32
+ let x;
33
+ if (c_0 || c_1) {
34
+ x = {};
35
+ const c_3 = $[3] !== other;
36
+ let t0;
37
+ if (c_3) {
38
+ t0 = foo(other);
39
+ $[3] = other;
40
+ $[4] = t0;
41
+ } else {
42
+ t0 = $[4];
43
+ }
44
+ if (t0) {
45
+ x.b = props.a.b;
46
+ } else {
47
+ x.c = props.a.b;
48
+ }
49
+ $[0] = other;
50
+ $[1] = props.a.b;
51
+ $[2] = x;
52
+ } else {
53
+ x = $[2];
54
+ }
55
+ return x;
56
+}
57
+
58
+```
59
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-ifelse.js
new
+13
@@ -0,0 +1,13 @@
1
+// props.a.b should be added as a unconditional dependency to the reactive
2
+// scope that produces x, since it is accessed unconditionally in all cfg
3
+// paths
4
+
5
+function TestCondDepInDirectIfElse(props, other) {
6
+ const x = {};
7
+ if (foo(other)) {
8
+ x.b = props.a.b;
9
+ } else {
10
+ x.c = props.a.b;
11
+ }
12
+ return x;
13
+}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-nested-ifelse-missing.expect.md
new
+68
@@ -0,0 +1,68 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should NOT be added as a unconditional dependency to the reactive
6
+// scope that produces x if it is not accessed in every path
7
+
8
+function TestCondDepInNestedIfElse(props, other) {
9
+ const x = {};
10
+ if (foo(other)) {
11
+ if (bar()) {
12
+ x.a = props.a.b;
13
+ }
14
+ } else {
15
+ x.d = props.a.b;
16
+ }
17
+ return x;
18
+}
19
+
20
+```
21
+
22
+## Code
23
+
24
+```javascript
25
+// props.a.b should NOT be added as a unconditional dependency to the reactive
26
+// scope that produces x if it is not accessed in every path
27
+
28
+function TestCondDepInNestedIfElse(props, other) {
29
+ const $ = React.unstable_useMemoCache(6);
30
+ const c_0 = $[0] !== other;
31
+ const c_1 = $[1] !== props;
32
+ let x;
33
+ if (c_0 || c_1) {
34
+ x = {};
35
+ const c_3 = $[3] !== other;
36
+ let t0;
37
+ if (c_3) {
38
+ t0 = foo(other);
39
+ $[3] = other;
40
+ $[4] = t0;
41
+ } else {
42
+ t0 = $[4];
43
+ }
44
+ if (t0) {
45
+ let t1;
46
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
47
+ t1 = bar();
48
+ $[5] = t1;
49
+ } else {
50
+ t1 = $[5];
51
+ }
52
+ if (t1) {
53
+ x.a = props.a.b;
54
+ }
55
+ } else {
56
+ x.d = props.a.b;
57
+ }
58
+ $[0] = other;
59
+ $[1] = props;
60
+ $[2] = x;
61
+ } else {
62
+ x = $[2];
63
+ }
64
+ return x;
65
+}
66
+
67
+```
68
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-nested-ifelse-missing.js
new
+14
@@ -0,0 +1,14 @@
1
+// props.a.b should NOT be added as a unconditional dependency to the reactive
2
+// scope that produces x if it is not accessed in every path
3
+
4
+function TestCondDepInNestedIfElse(props, other) {
5
+ const x = {};
6
+ if (foo(other)) {
7
+ if (bar()) {
8
+ x.a = props.a.b;
9
+ }
10
+ } else {
11
+ x.d = props.a.b;
12
+ }
13
+ return x;
14
+}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-nested-ifelse.expect.md
new
+89
@@ -0,0 +1,89 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should be added as a unconditional dependency to the reactive
6
+// scope that produces x, since it is accessed unconditionally in all cfg
7
+// paths
8
+
9
+function TestCondDepInNestedIfElse(props, other) {
10
+ const x = {};
11
+ if (foo(other)) {
12
+ if (bar()) {
13
+ x.a = props.a.b;
14
+ } else {
15
+ x.b = props.a.b;
16
+ }
17
+ } else if (baz(other)) {
18
+ x.c = props.a.b;
19
+ } else {
20
+ x.d = props.a.b;
21
+ }
22
+ return x;
23
+}
24
+
25
+```
26
+
27
+## Code
28
+
29
+```javascript
30
+// props.a.b should be added as a unconditional dependency to the reactive
31
+// scope that produces x, since it is accessed unconditionally in all cfg
32
+// paths
33
+
34
+function TestCondDepInNestedIfElse(props, other) {
35
+ const $ = React.unstable_useMemoCache(8);
36
+ const c_0 = $[0] !== other;
37
+ const c_1 = $[1] !== props.a.b;
38
+ let x;
39
+ if (c_0 || c_1) {
40
+ x = {};
41
+ const c_3 = $[3] !== other;
42
+ let t0;
43
+ if (c_3) {
44
+ t0 = foo(other);
45
+ $[3] = other;
46
+ $[4] = t0;
47
+ } else {
48
+ t0 = $[4];
49
+ }
50
+ if (t0) {
51
+ let t1;
52
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
53
+ t1 = bar();
54
+ $[5] = t1;
55
+ } else {
56
+ t1 = $[5];
57
+ }
58
+ if (t1) {
59
+ x.a = props.a.b;
60
+ } else {
61
+ x.b = props.a.b;
62
+ }
63
+ } else {
64
+ const c_6 = $[6] !== other;
65
+ let t2;
66
+ if (c_6) {
67
+ t2 = baz(other);
68
+ $[6] = other;
69
+ $[7] = t2;
70
+ } else {
71
+ t2 = $[7];
72
+ }
73
+ if (t2) {
74
+ x.c = props.a.b;
75
+ } else {
76
+ x.d = props.a.b;
77
+ }
78
+ }
79
+ $[0] = other;
80
+ $[1] = props.a.b;
81
+ $[2] = x;
82
+ } else {
83
+ x = $[2];
84
+ }
85
+ return x;
86
+}
87
+
88
+```
89
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-nested-ifelse.js
new
+19
@@ -0,0 +1,19 @@
1
+// props.a.b should be added as a unconditional dependency to the reactive
2
+// scope that produces x, since it is accessed unconditionally in all cfg
3
+// paths
4
+
5
+function TestCondDepInNestedIfElse(props, other) {
6
+ const x = {};
7
+ if (foo(other)) {
8
+ if (bar()) {
9
+ x.a = props.a.b;
10
+ } else {
11
+ x.b = props.a.b;
12
+ }
13
+ } else if (baz(other)) {
14
+ x.c = props.a.b;
15
+ } else {
16
+ x.d = props.a.b;
17
+ }
18
+ return x;
19
+}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-switch-missing-case.expect.md
new
+71
@@ -0,0 +1,71 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should NOT be added as a unconditional dependency to the reactive
6
+// scope that produces x if it is not accessed in every path
7
+
8
+function TestCondDepInSwitchMissingCase(props, other) {
9
+ const x = {};
10
+ switch (foo(other)) {
11
+ case 1:
12
+ x.a = props.a.b;
13
+ break;
14
+ case 2:
15
+ x.b = 42;
16
+ break;
17
+ default:
18
+ x.c = props.a.b;
19
+ break;
20
+ }
21
+ return x;
22
+}
23
+
24
+```
25
+
26
+## Code
27
+
28
+```javascript
29
+// props.a.b should NOT be added as a unconditional dependency to the reactive
30
+// scope that produces x if it is not accessed in every path
31
+
32
+function TestCondDepInSwitchMissingCase(props, other) {
33
+ const $ = React.unstable_useMemoCache(5);
34
+ const c_0 = $[0] !== other;
35
+ const c_1 = $[1] !== props;
36
+ let x;
37
+ if (c_0 || c_1) {
38
+ x = {};
39
+ const c_3 = $[3] !== other;
40
+ let t0;
41
+ if (c_3) {
42
+ t0 = foo(other);
43
+ $[3] = other;
44
+ $[4] = t0;
45
+ } else {
46
+ t0 = $[4];
47
+ }
48
+ bb1: switch (t0) {
49
+ case 1: {
50
+ x.a = props.a.b;
51
+ break bb1;
52
+ }
53
+ case 2: {
54
+ x.b = 42;
55
+ break bb1;
56
+ }
57
+ default: {
58
+ x.c = props.a.b;
59
+ }
60
+ }
61
+ $[0] = other;
62
+ $[1] = props;
63
+ $[2] = x;
64
+ } else {
65
+ x = $[2];
66
+ }
67
+ return x;
68
+}
69
+
70
+```
71
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-switch-missing-case.js
new
+18
@@ -0,0 +1,18 @@
1
+// props.a.b should NOT be added as a unconditional dependency to the reactive
2
+// scope that produces x if it is not accessed in every path
3
+
4
+function TestCondDepInSwitchMissingCase(props, other) {
5
+ const x = {};
6
+ switch (foo(other)) {
7
+ case 1:
8
+ x.a = props.a.b;
9
+ break;
10
+ case 2:
11
+ x.b = 42;
12
+ break;
13
+ default:
14
+ x.c = props.a.b;
15
+ break;
16
+ }
17
+ return x;
18
+}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-switch-missing-default.expect.md
new
+64
@@ -0,0 +1,64 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should NOT be added as a unconditional dependency to the reactive
6
+// scope that produces x if it is not accessed in the default case.
7
+
8
+function TestCondDepInSwitchMissingDefault(props, other) {
9
+ const x = {};
10
+ switch (foo(other)) {
11
+ case 1:
12
+ x.a = props.a.b;
13
+ break;
14
+ case 2:
15
+ x.b = props.a.b;
16
+ break;
17
+ }
18
+ return x;
19
+}
20
+
21
+```
22
+
23
+## Code
24
+
25
+```javascript
26
+// props.a.b should NOT be added as a unconditional dependency to the reactive
27
+// scope that produces x if it is not accessed in the default case.
28
+
29
+function TestCondDepInSwitchMissingDefault(props, other) {
30
+ const $ = React.unstable_useMemoCache(5);
31
+ const c_0 = $[0] !== other;
32
+ const c_1 = $[1] !== props;
33
+ let x;
34
+ if (c_0 || c_1) {
35
+ x = {};
36
+ const c_3 = $[3] !== other;
37
+ let t0;
38
+ if (c_3) {
39
+ t0 = foo(other);
40
+ $[3] = other;
41
+ $[4] = t0;
42
+ } else {
43
+ t0 = $[4];
44
+ }
45
+ bb1: switch (t0) {
46
+ case 1: {
47
+ x.a = props.a.b;
48
+ break bb1;
49
+ }
50
+ case 2: {
51
+ x.b = props.a.b;
52
+ }
53
+ }
54
+ $[0] = other;
55
+ $[1] = props;
56
+ $[2] = x;
57
+ } else {
58
+ x = $[2];
59
+ }
60
+ return x;
61
+}
62
+
63
+```
64
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-switch-missing-default.js
new
+15
@@ -0,0 +1,15 @@
1
+// props.a.b should NOT be added as a unconditional dependency to the reactive
2
+// scope that produces x if it is not accessed in the default case.
3
+
4
+function TestCondDepInSwitchMissingDefault(props, other) {
5
+ const x = {};
6
+ switch (foo(other)) {
7
+ case 1:
8
+ x.a = props.a.b;
9
+ break;
10
+ case 2:
11
+ x.b = props.a.b;
12
+ break;
13
+ }
14
+ return x;
15
+}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-switch.expect.md
new
+72
@@ -0,0 +1,72 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// props.a.b should be added as a unconditional dependency to the reactive
6
+// scope that produces x, since it is accessed unconditionally in all cfg
7
+// paths
8
+
9
+function TestCondDepInSwitch(props, other) {
10
+ const x = {};
11
+ switch (foo(other)) {
12
+ case 1:
13
+ x.a = props.a.b;
14
+ break;
15
+ case 2:
16
+ x.b = props.a.b;
17
+ break;
18
+ default:
19
+ x.c = props.a.b;
20
+ }
21
+ return x;
22
+}
23
+
24
+```
25
+
26
+## Code
27
+
28
+```javascript
29
+// props.a.b should be added as a unconditional dependency to the reactive
30
+// scope that produces x, since it is accessed unconditionally in all cfg
31
+// paths
32
+
33
+function TestCondDepInSwitch(props, other) {
34
+ const $ = React.unstable_useMemoCache(5);
35
+ const c_0 = $[0] !== other;
36
+ const c_1 = $[1] !== props.a.b;
37
+ let x;
38
+ if (c_0 || c_1) {
39
+ x = {};
40
+ const c_3 = $[3] !== other;
41
+ let t0;
42
+ if (c_3) {
43
+ t0 = foo(other);
44
+ $[3] = other;
45
+ $[4] = t0;
46
+ } else {
47
+ t0 = $[4];
48
+ }
49
+ bb1: switch (t0) {
50
+ case 1: {
51
+ x.a = props.a.b;
52
+ break bb1;
53
+ }
54
+ case 2: {
55
+ x.b = props.a.b;
56
+ break bb1;
57
+ }
58
+ default: {
59
+ x.c = props.a.b;
60
+ }
61
+ }
62
+ $[0] = other;
63
+ $[1] = props.a.b;
64
+ $[2] = x;
65
+ } else {
66
+ x = $[2];
67
+ }
68
+ return x;
69
+}
70
+
71
+```
72
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-cfg-switch.js
new
+18
@@ -0,0 +1,18 @@
1
+// props.a.b should be added as a unconditional dependency to the reactive
2
+// scope that produces x, since it is accessed unconditionally in all cfg
3
+// paths
4
+
5
+function TestCondDepInSwitch(props, other) {
6
+ const x = {};
7
+ switch (foo(other)) {
8
+ case 1:
9
+ x.a = props.a.b;
10
+ break;
11
+ case 2:
12
+ x.b = props.a.b;
13
+ break;
14
+ default:
15
+ x.c = props.a.b;
16
+ }
17
+ return x;
18
+}