@samitouri / QOS-React / commits / 6b129b59ed

[rhir] Refactor ReactiveScopeDependency, conditional dependencies (2/2)

--- See comment block in `PropagateScopeDependencies` and added test case `reduce-reactive-conditional-dependencies` for correctness properties / dependency merging logic.

Mofei Zhang committed Feb 28, 2023 at 16:35 UTC 6b129b59ed76d530882ecf1ea2e1eb7fc1bd6ad4
22 files changed +828 -210
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+195 -58
@@ -22,9 +22,7 @@ import {
22 ReactiveValue,
23 } from "../HIR/HIR";
24 import { eachInstructionValueOperand } from "../HIR/visitors";
25 -import { todoInvariant } from "../Utils/todo";
25 import { assertExhaustive } from "../Utils/utils";
27 -import { eachReactiveValueOperand } from "./visitors";
26
27 /**
28 * Infers the dependencies of each scope to include variables whose values
@@ -60,10 +58,10 @@ type Scopes = Array<ReactiveScope>;
58 // TODO(@mofeiZ): remove once we replace Context.#dependencies, #properties with tree
59 // representation
60 function areDependenciesEqual(
63 - dep1: ReactiveScopeDependency,
64 - dep2: ReactiveScopeDependency
61 + dep1: ReactiveScopeDependencyInfo,
62 + dep2: ReactiveScopeDependencyInfo
63 ): boolean {
66 - if (dep1.identifier.id !== dep2.identifier.id) {
64 + if (dep1.identifier.id !== dep2.identifier.id || dep1.cond !== dep2.cond) {
65 return false;
66 }
67 const dep1Path = dep1.path;
@@ -75,7 +73,7 @@ function areDependenciesEqual(
73 } else if (
74 dep1Path === null ||
75 dep2Path === null ||
78 - dep2Path.length != dep1Path.length
76 + dep2Path.length !== dep1Path.length
77 ) {
78 return false;
79 }
@@ -85,6 +83,8 @@ function areDependenciesEqual(
83 });
84 }
85
86 +type ReactiveScopeDependencyInfo = ReactiveScopeDependency & { cond: boolean };
87 +
88 /**
89 * Enum representing the access type of single property on a parent object.
90 * We distinguish on two independent axes:
@@ -97,23 +97,59 @@ function areDependenciesEqual(
97 * - Dependency: this property is read as a dependency and we must track changes
98 * to it for correctness.
99 *
100 + * ```javascript
101 + * // props.a is a dependency here and must be tracked
102 + * deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
103 + * // props.a is just an access here and does not need to be tracked
104 + * deps: {props.a.b} ---> minimalDeps: {props.a.b}
105 + * ```
106 */
107 enum PropertyAccessType {
108 + ConditionalAccess = "ConditionalAccess",
109 UnconditionalAccess = "UnconditionalAccess",
110 + ConditionalDependency = "ConditionalDependency",
111 UnconditionalDependency = "UnconditionalDependency",
112 }
113
114 +function isUnconditional(access: PropertyAccessType) {
115 + return (
116 + access === PropertyAccessType.UnconditionalAccess ||
117 + access === PropertyAccessType.UnconditionalDependency
118 + );
119 +}
120 +function isDependency(access: PropertyAccessType) {
121 + return (
122 + access === PropertyAccessType.ConditionalDependency ||
123 + access === PropertyAccessType.UnconditionalDependency
124 + );
125 +}
126 +
127 function merge(
128 access1: PropertyAccessType,
129 access2: PropertyAccessType
130 ): PropertyAccessType {
110 - if (
111 - access1 === PropertyAccessType.UnconditionalDependency ||
112 - access2 === PropertyAccessType.UnconditionalDependency
113 - ) {
114 - return PropertyAccessType.UnconditionalDependency;
131 + const resultIsUnconditional =
132 + isUnconditional(access1) || isUnconditional(access2);
133 + const resultIsDependency = isDependency(access1) || isDependency(access2);
134 +
135 + // Straightforward merge.
136 + // This can be represented as bitwise OR, but is written out for readability
137 + //
138 + // Observe that `UnconditionalAccess | ConditionalDependency` produces an
139 + // unconditionally accessed conditional dependency. We currently use these
140 + // as we use unconditional dependencies. (i.e. to codegen change variables)
141 + if (resultIsUnconditional) {
142 + if (resultIsDependency) {
143 + return PropertyAccessType.UnconditionalDependency;
144 + } else {
145 + return PropertyAccessType.UnconditionalAccess;
146 + }
147 } else {
116 - return PropertyAccessType.UnconditionalAccess;
148 + if (resultIsDependency) {
149 + return PropertyAccessType.ConditionalDependency;
150 + } else {
151 + return PropertyAccessType.ConditionalAccess;
152 + }
153 }
154 }
155
@@ -134,12 +170,24 @@ const promoteUncondResult = [
170 },
171 ];
172
173 +const promoteCondResult = [
174 + {
175 + relativePath: [],
176 + accessType: PropertyAccessType.ConditionalDependency,
177 + },
178 +];
179 +
180 +/**
181 + * Recursively calculates minimal dependencies in a subtree.
182 + * @param dep DependencyNode representing a dependency subtree.
183 + * @returns a minimal list of dependencies in this subtree.
184 + */
185 function deriveMinimalDependenciesInSubtree(
186 dep: DependencyNode
187 ): Array<ReduceResultNode> {
188 const results: Array<ReduceResultNode> = [];
189 for (const [childName, childNode] of dep.properties) {
142 - const reduceResult = deriveMinimalDependenciesInSubtree(childNode).map(
190 + const childResult = deriveMinimalDependenciesInSubtree(childNode).map(
191 ({ relativePath, accessType }) => {
192 return {
193 relativePath: [childName, ...relativePath],
@@ -147,7 +195,7 @@ function deriveMinimalDependenciesInSubtree(
195 };
196 }
197 );
150 - results.push(...reduceResult);
198 + results.push(...childResult);
199 }
200
201 switch (dep.accessType) {
@@ -155,13 +203,42 @@ function deriveMinimalDependenciesInSubtree(
203 return promoteUncondResult;
204 }
205 case PropertyAccessType.UnconditionalAccess: {
158 - // all children are unconditional dependencies, return them to preserve granularity
159 - return results;
206 + if (
207 + results.every(
208 + ({ accessType }) =>
209 + accessType === PropertyAccessType.UnconditionalDependency
210 + )
211 + ) {
212 + // all children are unconditional dependencies, return them to preserve granularity
213 + return results;
214 + } else {
215 + // at least one child is accessed conditionally, so this node needs to be promoted to
216 + // unconditional dependency
217 + return promoteUncondResult;
218 + }
219 + }
220 + case PropertyAccessType.ConditionalAccess:
221 + case PropertyAccessType.ConditionalDependency: {
222 + if (
223 + results.every(
224 + ({ accessType }) =>
225 + accessType === PropertyAccessType.ConditionalDependency
226 + )
227 + ) {
228 + // No children are accessed unconditionally, so we cannot promote this node to
229 + // unconditional access.
230 + // Truncate results of child nodes here, since we shouldn't access them anyways
231 + return promoteCondResult;
232 + } else {
233 + // at least one child is accessed unconditionally, so this node can be promoted to
234 + // unconditional dependency
235 + return promoteUncondResult;
236 + }
237 }
238 default: {
162 - todoInvariant(
163 - false,
164 - "[PropgateScopeDependencies] Handle conditional dependencies."
239 + assertExhaustive(
240 + dep.accessType,
241 + "[PropgateScopeDependencies] Unhandled access type!"
242 );
243 }
244 }
@@ -193,7 +270,7 @@ function deriveMinimalDependenciesInSubtree(
270 // TODO(@mofeiZ): change once we replace Context.#dependencies, #properties with tree
271 // representation
272 function deriveMinimalDependencies(
196 - initialDeps: Set<ReactiveScopeDependency>
273 + initialDeps: Set<ReactiveScopeDependencyInfo>
274 ): Set<ReactiveScopeDependency> {
275 const depRoots = new Map<IdentifierId, [Identifier, DependencyNode]>();
276
@@ -209,9 +286,13 @@ function deriveMinimalDependencies(
286 depRoots.set(dep.identifier.id, [dep.identifier, root]);
287 }
288 let currNode: DependencyNode = root;
212 - // TODO(@mofeiZ) add conditional access/dependencies here
213 - const accessType = PropertyAccessType.UnconditionalAccess;
214 - const depType = PropertyAccessType.UnconditionalDependency;
289 +
290 + const accessType = dep.cond
291 + ? PropertyAccessType.ConditionalAccess
292 + : PropertyAccessType.UnconditionalAccess;
293 + const depType = dep.cond
294 + ? PropertyAccessType.ConditionalDependency
295 + : PropertyAccessType.UnconditionalDependency;
296
297 for (const property of path) {
298 // all properties read 'on the way' to a dependency are marked as 'access'
@@ -256,20 +337,49 @@ function deriveMinimalDependencies(
337 class Context {
338 #declarations: DeclMap = new Map();
339 #reassignments: Map<Identifier, Decl> = new Map();
259 - #dependencies: Set<ReactiveScopeDependency> = new Set();
260 - #properties: Map<Identifier, ReactiveScopeDependency> = new Map();
340 + #dependencies: Set<ReactiveScopeDependencyInfo> = new Set();
341 + #properties: Map<Identifier, ReactiveScopeDependencyInfo> = new Map();
342 #temporaries: Map<Identifier, Place> = new Map();
343 + #inConditionalWithinScope: boolean = false;
344 #scopes: Scopes = [];
345
346 enter(scope: ReactiveScope, fn: () => void): Set<ReactiveScopeDependency> {
347 + // Save context of previous scope
348 + const prevInConditional = this.#inConditionalWithinScope;
349 const previousDependencies = this.#dependencies;
266 - const scopedDependencies = new Set<ReactiveScopeDependency>();
350 +
351 + // Set context for new scope
352 + // A nested scope should add all deps it directly uses as its own
353 + // unconditional deps, regardless of whether the nested scope is itself
354 + // within a conditional
355 + const scopedDependencies = new Set<ReactiveScopeDependencyInfo>();
356 + this.#inConditionalWithinScope = false;
357 this.#dependencies = scopedDependencies;
358 this.#scopes.push(scope);
359 +
360 fn();
361 +
362 + // Restore context of previous scope
363 this.#scopes.pop();
364 this.#dependencies = previousDependencies;
272 - return deriveMinimalDependencies(scopedDependencies);
365 + this.#inConditionalWithinScope = prevInConditional;
366 +
367 + const minScopeDependencies = deriveMinimalDependencies(scopedDependencies);
368 + // propagate dependencies upward using the same rules as normal dependency
369 + // collection. child scopes may have dependencies on values created within
370 + // the outer scope, which necessarily cannot be dependencies of the outer
371 + // scope
372 + for (const dep of minScopeDependencies) {
373 + this.visitDependency({ ...dep, cond: this.#inConditionalWithinScope });
374 + }
375 + return minScopeDependencies;
376 + }
377 +
378 + enterConditional(fn: () => void): void {
379 + const prevInConditional = this.#inConditionalWithinScope;
380 + this.#inConditionalWithinScope = true;
381 + fn();
382 + this.#inConditionalWithinScope = prevInConditional;
383 }
384
385 /**
@@ -292,16 +402,18 @@ class Context {
402 declareProperty(lvalue: Place, object: Place, property: string): void {
403 const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
404 const objectDependency = this.#properties.get(resolvedObject.identifier);
295 - let nextDependency: ReactiveScopeDependency;
405 + let nextDependency: ReactiveScopeDependencyInfo;
406 if (objectDependency === undefined) {
407 nextDependency = {
408 identifier: resolvedObject.identifier,
409 path: [property],
410 + cond: this.#inConditionalWithinScope,
411 };
412 } else {
413 nextDependency = {
414 identifier: objectDependency.identifier,
415 path: [...(objectDependency.path ?? []), property],
416 + cond: this.#inConditionalWithinScope,
417 };
418 }
419 this.#properties.set(lvalue.identifier, nextDependency);
@@ -317,29 +429,35 @@ class Context {
429
430 visitOperand(place: Place): void {
431 const resolved = this.#temporaries.get(place.identifier) ?? place;
320 - this.visitDependency({ identifier: resolved.identifier, path: null });
432 + this.visitDependency({
433 + identifier: resolved.identifier,
434 + path: null,
435 + cond: this.#inConditionalWithinScope,
436 + });
437 }
438
439 visitProperty(object: Place, property: string): void {
440 const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
441 const objectDependency = this.#properties.get(resolvedObject.identifier);
326 - let nextDependency: ReactiveScopeDependency;
442 + let nextDependency: ReactiveScopeDependencyInfo;
443 if (objectDependency === undefined) {
444 nextDependency = {
445 identifier: resolvedObject.identifier,
446 path: [property],
447 + cond: this.#inConditionalWithinScope,
448 };
449 } else {
450 nextDependency = {
451 identifier: objectDependency.identifier,
452 path: [...(objectDependency.path ?? []), property],
453 + cond: this.#inConditionalWithinScope,
454 };
455 }
456 this.visitDependency(nextDependency);
457 }
458
341 - visitDependency(dependency: ReactiveScopeDependency): void {
342 - let maybeDependency: ReactiveScopeDependency;
459 + visitDependency(dependency: ReactiveScopeDependencyInfo): void {
460 + let maybeDependency: ReactiveScopeDependencyInfo;
461 if (dependency.path !== null) {
462 // Operands may have memberPaths when propagating depenencies of an inner scope upward
463 // In this case we use the dependency as-is
@@ -349,12 +467,11 @@ class Context {
467 // the expanded Place. Fall back to using the operand as-is.
468 let propDep = this.#properties.get(dependency.identifier);
469 if (dependency.identifier.name === null && propDep !== undefined) {
352 - maybeDependency = propDep;
470 + maybeDependency = { ...propDep, cond: dependency.cond };
471 } else {
472 maybeDependency = dependency;
473 }
474 }
357 -
475 // Any value used after its originally defining scope has concluded must be added as an
476 // output of its defining scope. Regardless of whether its a const or not,
477 // some later code needs access to the value. If the current
@@ -429,13 +546,6 @@ function visit(context: Context, block: ReactiveBlock): void {
546 visit(context, item.instructions);
547 });
548 item.scope.dependencies = scopeDependencies;
432 - for (const dep of scopeDependencies) {
433 - // propagate dependencies upward using the same rules as
434 - // normal dependency collection. child scopes may have dependencies
435 - // on values created within the outer scope, which necessarily cannot
436 - // be dependencies of the outer scope
437 - context.visitDependency(dep);
438 - }
549 break;
550 }
551 case "instruction": {
@@ -462,30 +572,53 @@ function visit(context: Context, block: ReactiveBlock): void {
572 case "for": {
573 visitReactiveValue(context, terminal.init);
574 visitReactiveValue(context, terminal.test);
465 - visitReactiveValue(context, terminal.update);
466 - visit(context, terminal.loop);
575 + context.enterConditional(() => {
576 + visitReactiveValue(context, terminal.update);
577 + visit(context, terminal.loop);
578 + });
579 break;
580 }
581 case "while": {
582 visitReactiveValue(context, terminal.test);
471 - visit(context, terminal.loop);
583 + context.enterConditional(() => {
584 + visit(context, terminal.loop);
585 + });
586 break;
587 }
588 case "if": {
589 context.visitOperand(terminal.test);
476 - visit(context, terminal.consequent);
477 - if (terminal.alternate !== null) {
478 - visit(context, terminal.alternate);
479 - }
590 + /**
591 + * TODO: Track dependencies always accessed within consequent and ones
592 + * always accessed within alternate. If a dependency is always accessed in
593 + * both, we can promote it to an unconditional dependency.
594 + *
595 + * e.g. props.a.b is unconditionally accessed here.
596 + * if (foo(...)) {
597 + * access(props.a.b);
598 + * } else {
599 + * access(props.a.b);
600 + * }
601 + *
602 + * To deal with nested if-branches, enterConditional should return a list
603 + * of dependencies unconditionally accessed within the callback.
604 + */
605 + context.enterConditional(() => {
606 + visit(context, terminal.consequent);
607 + if (terminal.alternate !== null) {
608 + visit(context, terminal.alternate);
609 + }
610 + });
611 break;
612 }
613 case "switch": {
614 context.visitOperand(terminal.test);
484 - for (const case_ of terminal.cases) {
485 - if (case_.block !== undefined) {
486 - visit(context, case_.block);
615 + context.enterConditional(() => {
616 + for (const case_ of terminal.cases) {
617 + if (case_.block !== undefined) {
618 + visit(context, case_.block);
619 + }
620 }
488 - }
621 + });
622 break;
623 }
624 default: {
@@ -508,13 +641,19 @@ function visitReactiveValue(context: Context, value: ReactiveValue): void {
641 switch (value.kind) {
642 case "LogicalExpression": {
643 visitReactiveValue(context, value.left);
511 - visitReactiveValue(context, value.right);
644 +
645 + context.enterConditional(() => {
646 + visitReactiveValue(context, value.right);
647 + });
648 break;
649 }
650 case "ConditionalExpression": {
651 visitReactiveValue(context, value.test);
516 - visitReactiveValue(context, value.consequent);
517 - visitReactiveValue(context, value.alternate);
652 +
653 + context.enterConditional(() => {
654 + visitReactiveValue(context, value.consequent);
655 + visitReactiveValue(context, value.alternate);
656 + });
657 break;
658 }
659 case "SequenceExpression": {
@@ -553,9 +692,7 @@ function visitInstructionValue(
692 context.visitProperty(value.object, value.property);
693 }
694 } else {
556 - for (const operand of eachReactiveValueOperand(value)) {
557 - context.visitOperand(operand);
558 - }
695 + visitReactiveValue(context, value);
696 }
697 }
698
compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md
+18 -36
@@ -92,13 +92,10 @@ function ComponentA(props) {
92 * props.b *does* influence `a`
93 */
94 function ComponentB(props) {
95 - const $ = React.unstable_useMemoCache(5);
96 - const c_0 = $[0] !== props.a;
97 - const c_1 = $[1] !== props.b;
98 - const c_2 = $[2] !== props.c;
99 - const c_3 = $[3] !== props.d;
95 + const $ = React.unstable_useMemoCache(2);
96 + const c_0 = $[0] !== props;
97 let a;
101 - if (c_0 || c_1 || c_2 || c_3) {
98 + if (c_0) {
99 a = [];
100 a.push(props.a);
101 if (props.b) {
@@ -106,13 +103,10 @@ function ComponentB(props) {
103 }
104
105 a.push(props.d);
109 - $[0] = props.a;
110 - $[1] = props.b;
111 - $[2] = props.c;
112 - $[3] = props.d;
113 - $[4] = a;
106 + $[0] = props;
107 + $[1] = a;
108 } else {
115 - a = $[4];
109 + a = $[1];
110 }
111 return a;
112 }
@@ -121,13 +115,10 @@ function ComponentB(props) {
115 * props.b *does* influence `a`, but only in a way that is never observable
116 */
117 function ComponentC(props) {
124 - const $ = React.unstable_useMemoCache(5);
125 - const c_0 = $[0] !== props.a;
126 - const c_1 = $[1] !== props.b;
127 - const c_2 = $[2] !== props.c;
128 - const c_3 = $[3] !== props.d;
118 + const $ = React.unstable_useMemoCache(2);
119 + const c_0 = $[0] !== props;
120 let a;
130 - if (c_0 || c_1 || c_2 || c_3) {
121 + if (c_0) {
122 a = [];
123 a.push(props.a);
124 if (props.b) {
@@ -136,13 +127,10 @@ function ComponentC(props) {
127 }
128
129 a.push(props.d);
139 - $[0] = props.a;
140 - $[1] = props.b;
141 - $[2] = props.c;
142 - $[3] = props.d;
143 - $[4] = a;
130 + $[0] = props;
131 + $[1] = a;
132 } else {
145 - a = $[4];
133 + a = $[1];
134 }
135 return a;
136 }
@@ -151,13 +139,10 @@ function ComponentC(props) {
139 * props.b *does* influence `a`
140 */
141 function ComponentD(props) {
154 - const $ = React.unstable_useMemoCache(5);
155 - const c_0 = $[0] !== props.a;
156 - const c_1 = $[1] !== props.b;
157 - const c_2 = $[2] !== props.c;
158 - const c_3 = $[3] !== props.d;
142 + const $ = React.unstable_useMemoCache(2);
143 + const c_0 = $[0] !== props;
144 let a;
160 - if (c_0 || c_1 || c_2 || c_3) {
145 + if (c_0) {
146 a = [];
147 a.push(props.a);
148 if (props.b) {
@@ -166,13 +151,10 @@ function ComponentD(props) {
151 }
152
153 a.push(props.d);
169 - $[0] = props.a;
170 - $[1] = props.b;
171 - $[2] = props.c;
172 - $[3] = props.d;
173 - $[4] = a;
154 + $[0] = props;
155 + $[1] = a;
156 } else {
175 - a = $[4];
157 + a = $[1];
158 }
159 return a;
160 }
compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md
+30 -38
@@ -35,13 +35,11 @@ function mayMutate() {}
35
36 ```javascript
37 function ComponentA(props) {
38 - const $ = React.unstable_useMemoCache(8);
39 - const c_0 = $[0] !== props.p0;
40 - const c_1 = $[1] !== props.p1;
41 - const c_2 = $[2] !== props.p2;
38 + const $ = React.unstable_useMemoCache(6);
39 + const c_0 = $[0] !== props;
40 let a;
41 let b;
44 - if (c_0 || c_1 || c_2) {
42 + if (c_0) {
43 a = [];
44 b = [];
45 if (b) {
@@ -50,37 +48,33 @@ function ComponentA(props) {
48 if (props.p1) {
49 b.push(props.p2);
50 }
53 - $[0] = props.p0;
54 - $[1] = props.p1;
55 - $[2] = props.p2;
56 - $[3] = a;
57 - $[4] = b;
51 + $[0] = props;
52 + $[1] = a;
53 + $[2] = b;
54 } else {
59 - a = $[3];
60 - b = $[4];
55 + a = $[1];
56 + b = $[2];
57 }
62 - const c_5 = $[5] !== a;
63 - const c_6 = $[6] !== b;
58 + const c_3 = $[3] !== a;
59 + const c_4 = $[4] !== b;
60 let t0;
65 - if (c_5 || c_6) {
61 + if (c_3 || c_4) {
62 t0 = <Foo a={a} b={b}></Foo>;
67 - $[5] = a;
68 - $[6] = b;
69 - $[7] = t0;
63 + $[3] = a;
64 + $[4] = b;
65 + $[5] = t0;
66 } else {
71 - t0 = $[7];
67 + t0 = $[5];
68 }
69 return t0;
70 }
71
72 function ComponentB(props) {
77 - const $ = React.unstable_useMemoCache(8);
78 - const c_0 = $[0] !== props.p0;
79 - const c_1 = $[1] !== props.p1;
80 - const c_2 = $[2] !== props.p2;
73 + const $ = React.unstable_useMemoCache(6);
74 + const c_0 = $[0] !== props;
75 let a;
76 let b;
83 - if (c_0 || c_1 || c_2) {
77 + if (c_0) {
78 a = [];
79 b = [];
80 if (mayMutate(b)) {
@@ -89,25 +83,23 @@ function ComponentB(props) {
83 if (props.p1) {
84 b.push(props.p2);
85 }
92 - $[0] = props.p0;
93 - $[1] = props.p1;
94 - $[2] = props.p2;
95 - $[3] = a;
96 - $[4] = b;
86 + $[0] = props;
87 + $[1] = a;
88 + $[2] = b;
89 } else {
98 - a = $[3];
99 - b = $[4];
90 + a = $[1];
91 + b = $[2];
92 }
101 - const c_5 = $[5] !== a;
102 - const c_6 = $[6] !== b;
93 + const c_3 = $[3] !== a;
94 + const c_4 = $[4] !== b;
95 let t0;
104 - if (c_5 || c_6) {
96 + if (c_3 || c_4) {
97 t0 = <Foo a={a} b={b}></Foo>;
106 - $[5] = a;
107 - $[6] = b;
108 - $[7] = t0;
98 + $[3] = a;
99 + $[4] = b;
100 + $[5] = t0;
101 } else {
110 - t0 = $[7];
102 + t0 = $[5];
103 }
104 return t0;
105 }
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-no-uncond.expect.md new
+53
@@ -0,0 +1,53 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// When an object's properties are only read conditionally, we should
6 +// track the base object as a dependency.
7 +function TestOnlyConditionalDependencies(props, other) {
8 + const x = {};
9 + if (foo(other)) {
10 + x.b = props.a.b;
11 + x.c = props.a.b.c;
12 + }
13 + return x;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +// When an object's properties are only read conditionally, we should
22 +// track the base object as a dependency.
23 +function TestOnlyConditionalDependencies(props, other) {
24 + const $ = React.unstable_useMemoCache(5);
25 + const c_0 = $[0] !== other;
26 + const c_1 = $[1] !== props;
27 + let x;
28 + if (c_0 || c_1) {
29 + x = {};
30 + const c_3 = $[3] !== other;
31 + let t0;
32 + if (c_3) {
33 + t0 = foo(other);
34 + $[3] = other;
35 + $[4] = t0;
36 + } else {
37 + t0 = $[4];
38 + }
39 + if (t0) {
40 + x.b = props.a.b;
41 + x.c = props.a.b.c;
42 + }
43 + $[0] = other;
44 + $[1] = props;
45 + $[2] = x;
46 + } else {
47 + x = $[2];
48 + }
49 + return x;
50 +}
51 +
52 +```
53 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-no-uncond.js new
+10
@@ -0,0 +1,10 @@
1 +// When an object's properties are only read conditionally, we should
2 +// track the base object as a dependency.
3 +function TestOnlyConditionalDependencies(props, other) {
4 + const x = {};
5 + if (foo(other)) {
6 + x.b = props.a.b;
7 + x.c = props.a.b.c;
8 + }
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-promote-uncond.expect.md new
+55
@@ -0,0 +1,55 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// When a conditional dependency `props.a.b.c` has no unconditional dependency
6 +// in its subpath or superpath, we should find the nearest unconditional access
7 +// and promote it to an unconditional dependency.
8 +function TestPromoteUnconditionalAccessToDependency(props, other) {
9 + const x = {};
10 + x.a = props.a.a.a;
11 + if (foo(other)) {
12 + x.c = props.a.b.c;
13 + }
14 + return x;
15 +}
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +// When a conditional dependency `props.a.b.c` has no unconditional dependency
23 +// in its subpath or superpath, we should find the nearest unconditional access
24 +// and promote it to an unconditional dependency.
25 +function TestPromoteUnconditionalAccessToDependency(props, other) {
26 + const $ = React.unstable_useMemoCache(5);
27 + const c_0 = $[0] !== props.a;
28 + const c_1 = $[1] !== other;
29 + let x;
30 + if (c_0 || c_1) {
31 + x = {};
32 + x.a = props.a.a.a;
33 + const c_3 = $[3] !== other;
34 + let t0;
35 + if (c_3) {
36 + t0 = foo(other);
37 + $[3] = other;
38 + $[4] = t0;
39 + } else {
40 + t0 = $[4];
41 + }
42 + if (t0) {
43 + x.c = props.a.b.c;
44 + }
45 + $[0] = props.a;
46 + $[1] = other;
47 + $[2] = x;
48 + } else {
49 + x = $[2];
50 + }
51 + return x;
52 +}
53 +
54 +```
55 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-promote-uncond.js new
+11
@@ -0,0 +1,11 @@
1 +// When a conditional dependency `props.a.b.c` has no unconditional dependency
2 +// in its subpath or superpath, we should find the nearest unconditional access
3 +// and promote it to an unconditional dependency.
4 +function TestPromoteUnconditionalAccessToDependency(props, other) {
5 + const x = {};
6 + x.a = props.a.a.a;
7 + if (foo(other)) {
8 + x.c = props.a.b.c;
9 + }
10 + return x;
11 +}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-subpath-order1.expect.md new
+59
@@ -0,0 +1,59 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// When a conditional dependency `props.a` is a subpath of an unconditional
6 +// dependency `props.a.b`, we can access `props.a` while preserving program
7 +// semantics (with respect to nullthrows).
8 +// deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
9 +// ordering of accesses should not matter
10 +function TestConditionalSubpath1(props, other) {
11 + const x = {};
12 + x.b = props.a.b;
13 + if (foo(other)) {
14 + x.a = props.a;
15 + }
16 + return x;
17 +}
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +// When a conditional dependency `props.a` is a subpath of an unconditional
25 +// dependency `props.a.b`, we can access `props.a` while preserving program
26 +// semantics (with respect to nullthrows).
27 +// deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
28 +// ordering of accesses should not matter
29 +function TestConditionalSubpath1(props, other) {
30 + const $ = React.unstable_useMemoCache(5);
31 + const c_0 = $[0] !== props.a;
32 + const c_1 = $[1] !== other;
33 + let x;
34 + if (c_0 || c_1) {
35 + x = {};
36 + x.b = props.a.b;
37 + const c_3 = $[3] !== other;
38 + let t0;
39 + if (c_3) {
40 + t0 = foo(other);
41 + $[3] = other;
42 + $[4] = t0;
43 + } else {
44 + t0 = $[4];
45 + }
46 + if (t0) {
47 + x.a = props.a;
48 + }
49 + $[0] = props.a;
50 + $[1] = other;
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-subpath-order1.js new
+13
@@ -0,0 +1,13 @@
1 +// When a conditional dependency `props.a` is a subpath of an unconditional
2 +// dependency `props.a.b`, we can access `props.a` while preserving program
3 +// semantics (with respect to nullthrows).
4 +// deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
5 +// ordering of accesses should not matter
6 +function TestConditionalSubpath1(props, other) {
7 + const x = {};
8 + x.b = props.a.b;
9 + if (foo(other)) {
10 + x.a = props.a;
11 + }
12 + return x;
13 +}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-subpath-order2.expect.md new
+59
@@ -0,0 +1,59 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// When a conditional dependency `props.a` is a subpath of an unconditional
6 +// dependency `props.a.b`, we can access `props.a` while preserving program
7 +// semantics (with respect to nullthrows).
8 +// deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
9 +// ordering of accesses should not matter
10 +function TestConditionalSubpath2(props, other) {
11 + const x = {};
12 + if (foo(other)) {
13 + x.a = props.a;
14 + }
15 + x.b = props.a.b;
16 + return x;
17 +}
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +// When a conditional dependency `props.a` is a subpath of an unconditional
25 +// dependency `props.a.b`, we can access `props.a` while preserving program
26 +// semantics (with respect to nullthrows).
27 +// deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
28 +// ordering of accesses should not matter
29 +function TestConditionalSubpath2(props, other) {
30 + const $ = React.unstable_useMemoCache(5);
31 + const c_0 = $[0] !== other;
32 + const c_1 = $[1] !== props.a;
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 + if (t0) {
46 + x.a = props.a;
47 + }
48 + x.b = props.a.b;
49 + $[0] = other;
50 + $[1] = props.a;
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-subpath-order2.js new
+13
@@ -0,0 +1,13 @@
1 +// When a conditional dependency `props.a` is a subpath of an unconditional
2 +// dependency `props.a.b`, we can access `props.a` while preserving program
3 +// semantics (with respect to nullthrows).
4 +// deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
5 +// ordering of accesses should not matter
6 +function TestConditionalSubpath2(props, other) {
7 + const x = {};
8 + if (foo(other)) {
9 + x.a = props.a;
10 + }
11 + x.b = props.a.b;
12 + return x;
13 +}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-superpath-order1.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// When an unconditional dependency `props.a` is the subpath of a conditional
6 +// dependency `props.a.b`, we can safely overestimate and only track `props.a`
7 +// as a dependency
8 +// ordering of accesses should not matter
9 +function TestConditionalSuperpath1(props, other) {
10 + const x = {};
11 + x.a = props.a;
12 + if (foo(other)) {
13 + x.b = props.a.b;
14 + }
15 + return x;
16 +}
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +// When an unconditional dependency `props.a` is the subpath of a conditional
24 +// dependency `props.a.b`, we can safely overestimate and only track `props.a`
25 +// as a dependency
26 +// ordering of accesses should not matter
27 +function TestConditionalSuperpath1(props, other) {
28 + const $ = React.unstable_useMemoCache(5);
29 + const c_0 = $[0] !== props.a;
30 + const c_1 = $[1] !== other;
31 + let x;
32 + if (c_0 || c_1) {
33 + x = {};
34 + x.a = props.a;
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 + }
47 + $[0] = props.a;
48 + $[1] = other;
49 + $[2] = x;
50 + } else {
51 + x = $[2];
52 + }
53 + return x;
54 +}
55 +
56 +```
57 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-superpath-order1.js new
+12
@@ -0,0 +1,12 @@
1 +// When an unconditional dependency `props.a` is the subpath of a conditional
2 +// dependency `props.a.b`, we can safely overestimate and only track `props.a`
3 +// as a dependency
4 +// ordering of accesses should not matter
5 +function TestConditionalSuperpath1(props, other) {
6 + const x = {};
7 + x.a = props.a;
8 + if (foo(other)) {
9 + x.b = props.a.b;
10 + }
11 + return x;
12 +}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-superpath-order2.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// When an unconditional dependency `props.a` is the subpath of a conditional
6 +// dependency `props.a.b`, we can safely overestimate and only track `props.a`
7 +// as a dependency
8 +// ordering of accesses should not matter
9 +function TestConditionalSuperpath2(props, other) {
10 + const x = {};
11 + if (foo(other)) {
12 + x.b = props.a.b;
13 + }
14 + x.a = props.a;
15 + return x;
16 +}
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +// When an unconditional dependency `props.a` is the subpath of a conditional
24 +// dependency `props.a.b`, we can safely overestimate and only track `props.a`
25 +// as a dependency
26 +// ordering of accesses should not matter
27 +function TestConditionalSuperpath2(props, other) {
28 + const $ = React.unstable_useMemoCache(5);
29 + const c_0 = $[0] !== other;
30 + const c_1 = $[1] !== props.a;
31 + let x;
32 + if (c_0 || c_1) {
33 + x = {};
34 + const c_3 = $[3] !== other;
35 + let t0;
36 + if (c_3) {
37 + t0 = foo(other);
38 + $[3] = other;
39 + $[4] = t0;
40 + } else {
41 + t0 = $[4];
42 + }
43 + if (t0) {
44 + x.b = props.a.b;
45 + }
46 + x.a = props.a;
47 + $[0] = other;
48 + $[1] = props.a;
49 + $[2] = x;
50 + } else {
51 + x = $[2];
52 + }
53 + return x;
54 +}
55 +
56 +```
57 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-cond-deps-superpath-order2.js new
+12
@@ -0,0 +1,12 @@
1 +// When an unconditional dependency `props.a` is the subpath of a conditional
2 +// dependency `props.a.b`, we can safely overestimate and only track `props.a`
3 +// as a dependency
4 +// ordering of accesses should not matter
5 +function TestConditionalSuperpath2(props, other) {
6 + const x = {};
7 + if (foo(other)) {
8 + x.b = props.a.b;
9 + }
10 + x.a = props.a;
11 + return x;
12 +}
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-deps-cond-scope.expect.md new
+82
@@ -0,0 +1,82 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// Some reactive scopes are created within a conditional. If a child scope
6 +// is within a conditional, its reactive dependencies should be propagated
7 +// as conditionals
8 +//
9 +// In this test:
10 +// ```javascript
11 +// scope @0 (deps=[???] decls=[x]) {
12 +// const x = {};
13 +// if (foo) {
14 +// scope @1 (deps=[props.a.b] decls=[tmp]) {
15 +// const tmp = bar(props.a.b);
16 +// }
17 +// x.a = tmp;
18 +// }
19 +// }
20 +// return x;
21 +// ```
22 +
23 +function TestReactiveDepsInCondScope(props) {
24 + let x = {};
25 + if (foo) {
26 + let tmp = bar(props.a.b);
27 + x.a = tmp;
28 + }
29 + return x;
30 +}
31 +
32 +```
33 +
34 +## Code
35 +
36 +```javascript
37 +// Some reactive scopes are created within a conditional. If a child scope
38 +// is within a conditional, its reactive dependencies should be propagated
39 +// as conditionals
40 +//
41 +// In this test:
42 +// ```javascript
43 +// scope @0 (deps=[???] decls=[x]) {
44 +// const x = {};
45 +// if (foo) {
46 +// scope @1 (deps=[props.a.b] decls=[tmp]) {
47 +// const tmp = bar(props.a.b);
48 +// }
49 +// x.a = tmp;
50 +// }
51 +// }
52 +// return x;
53 +// ```
54 +
55 +function TestReactiveDepsInCondScope(props) {
56 + const $ = React.unstable_useMemoCache(4);
57 + const c_0 = $[0] !== props;
58 + let x;
59 + if (c_0) {
60 + x = {};
61 + if (foo) {
62 + const c_2 = $[2] !== props.a.b;
63 + let tmp;
64 + if (c_2) {
65 + tmp = bar(props.a.b);
66 + $[2] = props.a.b;
67 + $[3] = tmp;
68 + } else {
69 + tmp = $[3];
70 + }
71 + x.a = tmp;
72 + }
73 + $[0] = props;
74 + $[1] = x;
75 + } else {
76 + x = $[1];
77 + }
78 + return x;
79 +}
80 +
81 +```
82 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-deps-cond-scope.js new
+26
@@ -0,0 +1,26 @@
1 +// Some reactive scopes are created within a conditional. If a child scope
2 +// is within a conditional, its reactive dependencies should be propagated
3 +// as conditionals
4 +//
5 +// In this test:
6 +// ```javascript
7 +// scope @0 (deps=[???] decls=[x]) {
8 +// const x = {};
9 +// if (foo) {
10 +// scope @1 (deps=[props.a.b] decls=[tmp]) {
11 +// const tmp = bar(props.a.b);
12 +// }
13 +// x.a = tmp;
14 +// }
15 +// }
16 +// return x;
17 +// ```
18 +
19 +function TestReactiveDepsInCondScope(props) {
20 + let x = {};
21 + if (foo) {
22 + let tmp = bar(props.a.b);
23 + x.a = tmp;
24 + }
25 + return x;
26 +}
compiler/forget/src/__tests__/fixtures/hir/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md
+2 -2
@@ -57,12 +57,12 @@ function foo(props) {
57 } else {
58 x = $[1];
59 }
60 - const c_2 = $[2] !== props;
60 + const c_2 = $[2] !== props.showHeader;
61 const c_3 = $[3] !== x;
62 let t0;
63 if (c_2 || c_3) {
64 t0 = props.showHeader ? <div>{x}</div> : null;
65 - $[2] = props;
65 + $[2] = props.showHeader;
66 $[3] = x;
67 $[4] = t0;
68 } else {
compiler/forget/src/__tests__/fixtures/hir/ssa-leave-case.expect.md
+15 -17
@@ -23,41 +23,39 @@ function Component(props) {
23
24 ```javascript
25 function Component(props) {
26 - const $ = React.unstable_useMemoCache(7);
27 - const c_0 = $[0] !== props.p0;
28 - const c_1 = $[1] !== props.p1;
26 + const $ = React.unstable_useMemoCache(6);
27 + const c_0 = $[0] !== props;
28 let x;
29 let y;
31 - if (c_0 || c_1) {
30 + if (c_0) {
31 x = [];
32 y = undefined;
33 if (props.p0) {
34 x.push(props.p1);
35 y = x;
36 }
38 - $[0] = props.p0;
39 - $[1] = props.p1;
40 - $[2] = x;
41 - $[3] = y;
37 + $[0] = props;
38 + $[1] = x;
39 + $[2] = y;
40 } else {
43 - x = $[2];
44 - y = $[3];
41 + x = $[1];
42 + y = $[2];
43 }
46 - const c_4 = $[4] !== x;
47 - const c_5 = $[5] !== y;
44 + const c_3 = $[3] !== x;
45 + const c_4 = $[4] !== y;
46 let t0;
49 - if (c_4 || c_5) {
47 + if (c_3 || c_4) {
48 t0 = (
49 <Component>
50 {x}
51 {y}
52 </Component>
53 );
56 - $[4] = x;
57 - $[5] = y;
58 - $[6] = t0;
54 + $[3] = x;
55 + $[4] = y;
56 + $[5] = t0;
57 } else {
60 - t0 = $[6];
58 + t0 = $[5];
59 }
60 return t0;
61 }
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-with-mutation.expect.md
+6 -10
@@ -20,12 +20,10 @@ function foo(props) {
20
21 ```javascript
22 function foo(props) {
23 - const $ = React.unstable_useMemoCache(4);
24 - const c_0 = $[0] !== props.bar;
25 - const c_1 = $[1] !== props.cond;
26 - const c_2 = $[2] !== props.foo;
23 + const $ = React.unstable_useMemoCache(2);
24 + const c_0 = $[0] !== props;
25 let x;
28 - if (c_0 || c_1 || c_2) {
26 + if (c_0) {
27 x = [];
28 x.push(props.bar);
29 if (props.cond) {
@@ -34,12 +32,10 @@ function foo(props) {
32 }
33
34 mut(x);
37 - $[0] = props.bar;
38 - $[1] = props.cond;
39 - $[2] = props.foo;
40 - $[3] = x;
35 + $[0] = props;
36 + $[1] = x;
37 } else {
42 - x = $[3];
38 + x = $[1];
39 }
40 return x;
41 }
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md
+23 -25
@@ -32,12 +32,11 @@ function Component(props) {
32
33 ```javascript
34 function Component(props) {
35 - const $ = React.unstable_useMemoCache(10);
36 - const c_0 = $[0] !== props.p0;
37 - const c_1 = $[1] !== props.p2;
35 + const $ = React.unstable_useMemoCache(9);
36 + const c_0 = $[0] !== props;
37 let x;
38 let y;
40 - if (c_0 || c_1) {
39 + if (c_0) {
40 x = [];
41 y = undefined;
42 bb1: switch (props.p0) {
@@ -47,11 +46,11 @@ function Component(props) {
46 case true: {
47 x.push(props.p2);
48 let t0;
50 - if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
49 + if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
50 t0 = [];
52 - $[4] = t0;
51 + $[3] = t0;
52 } else {
54 - t0 = $[4];
53 + t0 = $[3];
54 }
55 y = t0;
56 break bb1;
@@ -63,34 +62,33 @@ function Component(props) {
62 y = x;
63 }
64 }
66 - $[0] = props.p0;
67 - $[1] = props.p2;
68 - $[2] = x;
69 - $[3] = y;
65 + $[0] = props;
66 + $[1] = x;
67 + $[2] = y;
68 } else {
71 - x = $[2];
72 - y = $[3];
69 + x = $[1];
70 + y = $[2];
71 }
74 - const c_5 = $[5] !== x;
72 + const c_4 = $[4] !== x;
73 let child;
76 - if (c_5) {
74 + if (c_4) {
75 child = <Component data={x}></Component>;
78 - $[5] = x;
79 - $[6] = child;
76 + $[4] = x;
77 + $[5] = child;
78 } else {
81 - child = $[6];
79 + child = $[5];
80 }
81 y.push(props.p4);
84 - const c_7 = $[7] !== y;
85 - const c_8 = $[8] !== child;
82 + const c_6 = $[6] !== y;
83 + const c_7 = $[7] !== child;
84 let t1;
87 - if (c_7 || c_8) {
85 + if (c_6 || c_7) {
86 t1 = <Component data={y}>{child}</Component>;
89 - $[7] = y;
90 - $[8] = child;
91 - $[9] = t1;
87 + $[6] = y;
88 + $[7] = child;
89 + $[8] = t1;
90 } else {
93 - t1 = $[9];
91 + t1 = $[8];
92 }
93 return t1;
94 }
compiler/forget/src/__tests__/fixtures/hir/switch.expect.md
+20 -24
@@ -27,13 +27,11 @@ function Component(props) {
27
28 ```javascript
29 function Component(props) {
30 - const $ = React.unstable_useMemoCache(10);
31 - const c_0 = $[0] !== props.p0;
32 - const c_1 = $[1] !== props.p2;
33 - const c_2 = $[2] !== props.p3;
30 + const $ = React.unstable_useMemoCache(8);
31 + const c_0 = $[0] !== props;
32 let x;
33 let y;
36 - if (c_0 || c_1 || c_2) {
34 + if (c_0) {
35 x = [];
36 y = undefined;
37 switch (props.p0) {
@@ -45,35 +43,33 @@ function Component(props) {
43 y = x;
44 }
45 }
48 - $[0] = props.p0;
49 - $[1] = props.p2;
50 - $[2] = props.p3;
51 - $[3] = x;
52 - $[4] = y;
46 + $[0] = props;
47 + $[1] = x;
48 + $[2] = y;
49 } else {
54 - x = $[3];
55 - y = $[4];
50 + x = $[1];
51 + y = $[2];
52 }
57 - const c_5 = $[5] !== x;
53 + const c_3 = $[3] !== x;
54 let child;
59 - if (c_5) {
55 + if (c_3) {
56 child = <Component data={x}></Component>;
61 - $[5] = x;
62 - $[6] = child;
57 + $[3] = x;
58 + $[4] = child;
59 } else {
64 - child = $[6];
60 + child = $[4];
61 }
62 y.push(props.p4);
67 - const c_7 = $[7] !== y;
68 - const c_8 = $[8] !== child;
63 + const c_5 = $[5] !== y;
64 + const c_6 = $[6] !== child;
65 let t0;
70 - if (c_7 || c_8) {
66 + if (c_5 || c_6) {
67 t0 = <Component data={y}>{child}</Component>;
72 - $[7] = y;
73 - $[8] = child;
74 - $[9] = t0;
68 + $[5] = y;
69 + $[6] = child;
70 + $[7] = t0;
71 } else {
76 - t0 = $[9];
72 + t0 = $[7];
73 }
74 return t0;
75 }