@samitouri / QOS-React-1 / commits / 1460d67c5b

[compiler][hir] Only hoist always-accessed PropertyLoads from function decls (#31066)

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom): * __->__ #31066 * #31032 Prior to this PR, we consider all of a nested function's accessed paths as 'hoistable' (to the basic block in which the function was defined). Now, we traverse nested functions and find all paths hoistable to their *entry block*. Note that this only replaces the *hoisting* part of function declarations, not dependencies. This realistically only affects optional chains within functions, which always get truncated to its inner non-optional path (see [todo-infer-function-uncond-optionals-hoisted.tsx](https://github.com/facebook/react/blob/576f3c0aa898cb99da1b7bf15317756e25c13708/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx)) See newly added test fixtures for details Update: Note that toggling `enableTreatFunctionDepsAsConditional` makes a non-trivial impact on granularity of inferred deps (i.e. we find that function declarations uniquely identify some paths as hoistable). Snapshot comparison of internal code shows ~2.5% of files get worse dependencies ([internal link](https://www.internalfb.com/phabricator/paste/view/P1625792186))

mofeiZ committed Oct 3, 2024 at 14:41 UTC 1460d67c5b9a0d4498b4d22e1a5a6c0ccac85fdd
27 files changed +1306 -75
compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts
+161 -70
@@ -7,6 +7,7 @@ import {
7 Set_union,
8 getOrInsertDefault,
9 } from '../Utils/utils';
10 +import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
11 import {
12 BasicBlock,
13 BlockId,
@@ -15,10 +16,12 @@ import {
16 HIRFunction,
17 Identifier,
18 IdentifierId,
19 + InstructionId,
20 InstructionValue,
21 ReactiveScopeDependency,
22 ScopeId,
23 } from './HIR';
24 +import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR';
25
26 /**
27 * Helper function for `PropagateScopeDependencies`. Uses control flow graph
@@ -83,28 +86,57 @@ export function collectHoistablePropertyLoads(
86 fn: HIRFunction,
87 temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
88 hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
86 -): ReadonlyMap<ScopeId, BlockInfo> {
89 + nestedFnImmutableContext: ReadonlySet<IdentifierId> | null,
90 +): ReadonlyMap<BlockId, BlockInfo> {
91 const registry = new PropertyPathRegistry();
92
89 - const nodes = collectNonNullsInBlocks(
90 - fn,
91 - temporaries,
93 + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn);
94 + const actuallyEvaluatedTemporaries = new Map(
95 + [...temporaries].filter(([id]) => !functionExpressionLoads.has(id)),
96 + );
97 +
98 + /**
99 + * Due to current limitations of mutable range inference, there are edge cases in
100 + * which we infer known-immutable values (e.g. props or hook params) to have a
101 + * mutable range and scope.
102 + * (see `destructure-array-declaration-to-context-var` fixture)
103 + * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
104 + * is being rewritten to HIR).
105 + */
106 + const knownImmutableIdentifiers = new Set<IdentifierId>();
107 + if (fn.fnType === 'Component' || fn.fnType === 'Hook') {
108 + for (const p of fn.params) {
109 + if (p.kind === 'Identifier') {
110 + knownImmutableIdentifiers.add(p.identifier.id);
111 + }
112 + }
113 + }
114 + const nodes = collectNonNullsInBlocks(fn, {
115 + temporaries: actuallyEvaluatedTemporaries,
116 + knownImmutableIdentifiers,
117 hoistableFromOptionals,
118 registry,
94 - );
119 + nestedFnImmutableContext,
120 + });
121 propagateNonNull(fn, nodes, registry);
122
97 - const nodesKeyedByScopeId = new Map<ScopeId, BlockInfo>();
123 + return nodes;
124 +}
125 +
126 +export function keyByScopeId<T>(
127 + fn: HIRFunction,
128 + source: ReadonlyMap<BlockId, T>,
129 +): ReadonlyMap<ScopeId, T> {
130 + const keyedByScopeId = new Map<ScopeId, T>();
131 for (const [_, block] of fn.body.blocks) {
132 if (block.terminal.kind === 'scope') {
100 - nodesKeyedByScopeId.set(
133 + keyedByScopeId.set(
134 block.terminal.scope.id,
102 - nodes.get(block.terminal.block)!,
135 + source.get(block.terminal.block)!,
136 );
137 }
138 }
106 -
107 - return nodesKeyedByScopeId;
139 + return keyedByScopeId;
140 }
141
142 export type BlockInfo = {
@@ -211,45 +243,75 @@ class PropertyPathRegistry {
243
244 function getMaybeNonNullInInstruction(
245 instr: InstructionValue,
214 - temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
215 - registry: PropertyPathRegistry,
246 + context: CollectNonNullsInBlocksContext,
247 ): PropertyPathNode | null {
248 let path = null;
249 if (instr.kind === 'PropertyLoad') {
219 - path = temporaries.get(instr.object.identifier.id) ?? {
250 + path = context.temporaries.get(instr.object.identifier.id) ?? {
251 identifier: instr.object.identifier,
252 path: [],
253 };
254 } else if (instr.kind === 'Destructure') {
224 - path = temporaries.get(instr.value.identifier.id) ?? null;
255 + path = context.temporaries.get(instr.value.identifier.id) ?? null;
256 } else if (instr.kind === 'ComputedLoad') {
226 - path = temporaries.get(instr.object.identifier.id) ?? null;
257 + path = context.temporaries.get(instr.object.identifier.id) ?? null;
258 + }
259 + return path != null ? context.registry.getOrCreateProperty(path) : null;
260 +}
261 +
262 +function isImmutableAtInstr(
263 + identifier: Identifier,
264 + instr: InstructionId,
265 + context: CollectNonNullsInBlocksContext,
266 +): boolean {
267 + if (context.nestedFnImmutableContext != null) {
268 + /**
269 + * Comparing instructions ids across inner-outer function bodies is not valid, as they are numbered
270 + */
271 + return context.nestedFnImmutableContext.has(identifier.id);
272 + } else {
273 + /**
274 + * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
275 + * are not valid with respect to current instruction id numbering.
276 + * We use attached reactive scope ranges as a proxy for mutable range, but this
277 + * is an overestimate as (1) scope ranges merge and align to form valid program
278 + * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
279 + * non-mutable identifiers.
280 + *
281 + * See comment in exported function for why we track known immutable identifiers.
282 + */
283 + const mutableAtInstr =
284 + identifier.mutableRange.end > identifier.mutableRange.start + 1 &&
285 + identifier.scope != null &&
286 + inRange(
287 + {
288 + id: instr,
289 + },
290 + identifier.scope.range,
291 + );
292 + return (
293 + !mutableAtInstr || context.knownImmutableIdentifiers.has(identifier.id)
294 + );
295 }
228 - return path != null ? registry.getOrCreateProperty(path) : null;
296 }
297
298 +type CollectNonNullsInBlocksContext = {
299 + temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
300 + knownImmutableIdentifiers: ReadonlySet<IdentifierId>;
301 + hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>;
302 + registry: PropertyPathRegistry;
303 + /**
304 + * (For nested / inner function declarations)
305 + * Context variables (i.e. captured from an outer scope) that are immutable.
306 + * Note that this technically could be merged into `knownImmutableIdentifiers`,
307 + * but are currently kept separate for readability.
308 + */
309 + nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
310 +};
311 function collectNonNullsInBlocks(
312 fn: HIRFunction,
233 - temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
234 - hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
235 - registry: PropertyPathRegistry,
313 + context: CollectNonNullsInBlocksContext,
314 ): ReadonlyMap<BlockId, BlockInfo> {
237 - /**
238 - * Due to current limitations of mutable range inference, there are edge cases in
239 - * which we infer known-immutable values (e.g. props or hook params) to have a
240 - * mutable range and scope.
241 - * (see `destructure-array-declaration-to-context-var` fixture)
242 - * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
243 - * is being rewritten to HIR).
244 - */
245 - const knownImmutableIdentifiers = new Set<IdentifierId>();
246 - if (fn.fnType === 'Component' || fn.fnType === 'Hook') {
247 - for (const p of fn.params) {
248 - if (p.kind === 'Identifier') {
249 - knownImmutableIdentifiers.add(p.identifier.id);
250 - }
251 - }
252 - }
315 /**
316 * Known non-null objects such as functional component props can be safely
317 * read from any block.
@@ -261,7 +323,9 @@ function collectNonNullsInBlocks(
323 fn.params[0].kind === 'Identifier'
324 ) {
325 const identifier = fn.params[0].identifier;
264 - knownNonNullIdentifiers.add(registry.getOrCreateIdentifier(identifier));
326 + knownNonNullIdentifiers.add(
327 + context.registry.getOrCreateIdentifier(identifier),
328 + );
329 }
330 const nodes = new Map<BlockId, BlockInfo>();
331 for (const [_, block] of fn.body.blocks) {
@@ -269,45 +333,48 @@ function collectNonNullsInBlocks(
333 knownNonNullIdentifiers,
334 );
335
272 - const maybeOptionalChain = hoistableFromOptionals.get(block.id);
336 + const maybeOptionalChain = context.hoistableFromOptionals.get(block.id);
337 if (maybeOptionalChain != null) {
338 assumedNonNullObjects.add(
275 - registry.getOrCreateProperty(maybeOptionalChain),
339 + context.registry.getOrCreateProperty(maybeOptionalChain),
340 );
341 }
342 for (const instr of block.instructions) {
279 - const maybeNonNull = getMaybeNonNullInInstruction(
280 - instr.value,
281 - temporaries,
282 - registry,
283 - );
284 - if (maybeNonNull != null) {
285 - const baseIdentifier = maybeNonNull.fullPath.identifier;
286 - /**
287 - * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
288 - * are not valid with respect to current instruction id numbering.
289 - * We use attached reactive scope ranges as a proxy for mutable range, but this
290 - * is an overestimate as (1) scope ranges merge and align to form valid program
291 - * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
292 - * non-mutable identifiers.
293 - *
294 - * See comment at top of function for why we track known immutable identifiers.
295 - */
296 - const isMutableAtInstr =
297 - baseIdentifier.mutableRange.end >
298 - baseIdentifier.mutableRange.start + 1 &&
299 - baseIdentifier.scope != null &&
300 - inRange(
301 - {
302 - id: instr.id,
303 - },
304 - baseIdentifier.scope.range,
305 - );
306 - if (
307 - !isMutableAtInstr ||
308 - knownImmutableIdentifiers.has(baseIdentifier.id)
309 - ) {
310 - assumedNonNullObjects.add(maybeNonNull);
343 + const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context);
344 + if (
345 + maybeNonNull != null &&
346 + isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context)
347 + ) {
348 + assumedNonNullObjects.add(maybeNonNull);
349 + }
350 + if (
351 + instr.value.kind === 'FunctionExpression' &&
352 + !fn.env.config.enableTreatFunctionDepsAsConditional
353 + ) {
354 + const innerFn = instr.value.loweredFunc;
355 + const innerTemporaries = collectTemporariesSidemap(
356 + innerFn.func,
357 + new Set(),
358 + );
359 + const innerOptionals = collectOptionalChainSidemap(innerFn.func);
360 + const innerHoistableMap = collectHoistablePropertyLoads(
361 + innerFn.func,
362 + innerTemporaries,
363 + innerOptionals.hoistableObjects,
364 + context.nestedFnImmutableContext ??
365 + new Set(
366 + innerFn.func.context
367 + .filter(place =>
368 + isImmutableAtInstr(place.identifier, instr.id, context),
369 + )
370 + .map(place => place.identifier.id),
371 + ),
372 + );
373 + const innerHoistables = assertNonNull(
374 + innerHoistableMap.get(innerFn.func.body.entry),
375 + );
376 + for (const entry of innerHoistables.assumedNonNullObjects) {
377 + assumedNonNullObjects.add(entry);
378 }
379 }
380 }
@@ -515,3 +582,27 @@ function reduceMaybeOptionalChains(
582 }
583 } while (changed);
584 }
585 +
586 +function collectFunctionExpressionFakeLoads(
587 + fn: HIRFunction,
588 +): Set<IdentifierId> {
589 + const sources = new Map<IdentifierId, IdentifierId>();
590 + const functionExpressionReferences = new Set<IdentifierId>();
591 +
592 + for (const [_, block] of fn.body.blocks) {
593 + for (const {lvalue, value} of block.instructions) {
594 + if (value.kind === 'FunctionExpression') {
595 + for (const reference of value.loweredFunc.dependencies) {
596 + let curr: IdentifierId | undefined = reference.identifier.id;
597 + while (curr != null) {
598 + functionExpressionReferences.add(curr);
599 + curr = sources.get(curr);
600 + }
601 + }
602 + } else if (value.kind === 'PropertyLoad') {
603 + sources.set(lvalue.identifier.id, value.object.identifier.id);
604 + }
605 + }
606 + }
607 + return functionExpressionReferences;
608 +}
compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts
+7 -5
@@ -17,7 +17,10 @@ import {
17 areEqualPaths,
18 IdentifierId,
19 } from './HIR';
20 -import {collectHoistablePropertyLoads} from './CollectHoistablePropertyLoads';
20 +import {
21 + collectHoistablePropertyLoads,
22 + keyByScopeId,
23 +} from './CollectHoistablePropertyLoads';
24 import {
25 ScopeBlockTraversal,
26 eachInstructionOperand,
@@ -41,10 +44,9 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
44 hoistableObjects,
45 } = collectOptionalChainSidemap(fn);
46
44 - const hoistablePropertyLoads = collectHoistablePropertyLoads(
47 + const hoistablePropertyLoads = keyByScopeId(
48 fn,
46 - temporaries,
47 - hoistableObjects,
49 + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null),
50 );
51
52 const scopeDeps = collectDependencies(
@@ -209,7 +211,7 @@ function findTemporariesUsedOutsideDeclaringScope(
211 * of $1, as the evaluation of `arr.length` changes between instructions $1 and
212 * $3. We do not track $1 -> arr.length in this case.
213 */
212 -function collectTemporariesSidemap(
214 +export function collectTemporariesSidemap(
215 fn: HIRFunction,
216 usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
217 ): ReadonlyMap<IdentifierId, ReactiveScopeDependency> {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.expect.md new
+97
@@ -0,0 +1,97 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {shallowCopy, mutate, Stringify} from 'shared-runtime';
8 +
9 +function useFoo({
10 + a,
11 + shouldReadA,
12 +}: {
13 + a: {b: {c: number}; x: number};
14 + shouldReadA: boolean;
15 +}) {
16 + const local = shallowCopy(a);
17 + mutate(local);
18 + return (
19 + <Stringify
20 + fn={() => {
21 + if (shouldReadA) return local.b.c;
22 + return null;
23 + }}
24 + shouldInvokeFns={true}
25 + />
26 + );
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: useFoo,
31 + params: [{a: null, shouldReadA: true}],
32 + sequentialRenders: [
33 + {a: null, shouldReadA: true},
34 + {a: null, shouldReadA: false},
35 + {a: {b: {c: 4}}, shouldReadA: true},
36 + ],
37 +};
38 +
39 +```
40 +
41 +## Code
42 +
43 +```javascript
44 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
45 +
46 +import { shallowCopy, mutate, Stringify } from "shared-runtime";
47 +
48 +function useFoo(t0) {
49 + const $ = _c(5);
50 + const { a, shouldReadA } = t0;
51 + let local;
52 + if ($[0] !== a) {
53 + local = shallowCopy(a);
54 + mutate(local);
55 + $[0] = a;
56 + $[1] = local;
57 + } else {
58 + local = $[1];
59 + }
60 + let t1;
61 + if ($[2] !== shouldReadA || $[3] !== local) {
62 + t1 = (
63 + <Stringify
64 + fn={() => {
65 + if (shouldReadA) {
66 + return local.b.c;
67 + }
68 + return null;
69 + }}
70 + shouldInvokeFns={true}
71 + />
72 + );
73 + $[2] = shouldReadA;
74 + $[3] = local;
75 + $[4] = t1;
76 + } else {
77 + t1 = $[4];
78 + }
79 + return t1;
80 +}
81 +
82 +export const FIXTURE_ENTRYPOINT = {
83 + fn: useFoo,
84 + params: [{ a: null, shouldReadA: true }],
85 + sequentialRenders: [
86 + { a: null, shouldReadA: true },
87 + { a: null, shouldReadA: false },
88 + { a: { b: { c: 4 } }, shouldReadA: true },
89 + ],
90 +};
91 +
92 +```
93 +
94 +### Eval output
95 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
96 +<div>{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}</div>
97 +<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-local-var.tsx new
+33
@@ -0,0 +1,33 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {shallowCopy, mutate, Stringify} from 'shared-runtime';
4 +
5 +function useFoo({
6 + a,
7 + shouldReadA,
8 +}: {
9 + a: {b: {c: number}; x: number};
10 + shouldReadA: boolean;
11 +}) {
12 + const local = shallowCopy(a);
13 + mutate(local);
14 + return (
15 + <Stringify
16 + fn={() => {
17 + if (shouldReadA) return local.b.c;
18 + return null;
19 + }}
20 + shouldInvokeFns={true}
21 + />
22 + );
23 +}
24 +
25 +export const FIXTURE_ENTRYPOINT = {
26 + fn: useFoo,
27 + params: [{a: null, shouldReadA: true}],
28 + sequentialRenders: [
29 + {a: null, shouldReadA: true},
30 + {a: null, shouldReadA: false},
31 + {a: {b: {c: 4}}, shouldReadA: true},
32 + ],
33 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.expect.md new
+80
@@ -0,0 +1,80 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {Stringify} from 'shared-runtime';
8 +
9 +function Foo({a, shouldReadA}) {
10 + return (
11 + <Stringify
12 + fn={() => {
13 + if (shouldReadA) return a.b.c;
14 + return null;
15 + }}
16 + shouldInvokeFns={true}
17 + />
18 + );
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: Foo,
23 + params: [{a: null, shouldReadA: true}],
24 + sequentialRenders: [
25 + {a: null, shouldReadA: true},
26 + {a: null, shouldReadA: false},
27 + {a: {b: {c: 4}}, shouldReadA: true},
28 + ],
29 +};
30 +
31 +```
32 +
33 +## Code
34 +
35 +```javascript
36 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
37 +
38 +import { Stringify } from "shared-runtime";
39 +
40 +function Foo(t0) {
41 + const $ = _c(3);
42 + const { a, shouldReadA } = t0;
43 + let t1;
44 + if ($[0] !== shouldReadA || $[1] !== a) {
45 + t1 = (
46 + <Stringify
47 + fn={() => {
48 + if (shouldReadA) {
49 + return a.b.c;
50 + }
51 + return null;
52 + }}
53 + shouldInvokeFns={true}
54 + />
55 + );
56 + $[0] = shouldReadA;
57 + $[1] = a;
58 + $[2] = t1;
59 + } else {
60 + t1 = $[2];
61 + }
62 + return t1;
63 +}
64 +
65 +export const FIXTURE_ENTRYPOINT = {
66 + fn: Foo,
67 + params: [{ a: null, shouldReadA: true }],
68 + sequentialRenders: [
69 + { a: null, shouldReadA: true },
70 + { a: null, shouldReadA: false },
71 + { a: { b: { c: 4 } }, shouldReadA: true },
72 + ],
73 +};
74 +
75 +```
76 +
77 +### Eval output
78 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
79 +<div>{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}</div>
80 +<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-cond-access-not-hoisted.tsx new
+25
@@ -0,0 +1,25 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {Stringify} from 'shared-runtime';
4 +
5 +function Foo({a, shouldReadA}) {
6 + return (
7 + <Stringify
8 + fn={() => {
9 + if (shouldReadA) return a.b.c;
10 + return null;
11 + }}
12 + shouldInvokeFns={true}
13 + />
14 + );
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Foo,
19 + params: [{a: null, shouldReadA: true}],
20 + sequentialRenders: [
21 + {a: null, shouldReadA: true},
22 + {a: null, shouldReadA: false},
23 + {a: {b: {c: 4}}, shouldReadA: true},
24 + ],
25 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md new
+52
@@ -0,0 +1,52 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {Stringify} from 'shared-runtime';
8 +
9 +function useFoo({a}) {
10 + return <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: useFoo,
15 + params: [{a: null}],
16 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
17 +};
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
25 +
26 +import { Stringify } from "shared-runtime";
27 +
28 +function useFoo(t0) {
29 + const $ = _c(2);
30 + const { a } = t0;
31 + let t1;
32 + if ($[0] !== a.b.c) {
33 + t1 = <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
34 + $[0] = a.b.c;
35 + $[1] = t1;
36 + } else {
37 + t1 = $[1];
38 + }
39 + return t1;
40 +}
41 +
42 +export const FIXTURE_ENTRYPOINT = {
43 + fn: useFoo,
44 + params: [{ a: null }],
45 + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
46 +};
47 +
48 +```
49 +
50 +### Eval output
51 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
52 +<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.tsx new
+13
@@ -0,0 +1,13 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {Stringify} from 'shared-runtime';
4 +
5 +function useFoo({a}) {
6 + return <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: useFoo,
11 + params: [{a: null}],
12 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
13 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md new
+92
@@ -0,0 +1,92 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
8 +
9 +function Foo({a, cond}) {
10 + // Assume fn will be uncond evaluated, so we can safely evaluate {a.<any>,
11 + // a.b.<any}
12 + const fn = () => [a, a.b.c];
13 + useIdentity(null);
14 + const x = makeArray();
15 + if (cond) {
16 + x.push(identity(a.b.c));
17 + }
18 + return <Stringify fn={fn} x={x} shouldInvokeFns={true} />;
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: Foo,
23 + params: [{a: null, cond: true}],
24 + sequentialRenders: [
25 + {a: null, cond: true},
26 + {a: {b: {c: 4}}, cond: true},
27 + {a: {b: {c: 4}}, cond: true},
28 + ],
29 +};
30 +
31 +```
32 +
33 +## Code
34 +
35 +```javascript
36 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
37 +
38 +import { identity, makeArray, Stringify, useIdentity } from "shared-runtime";
39 +
40 +function Foo(t0) {
41 + const $ = _c(8);
42 + const { a, cond } = t0;
43 + let t1;
44 + if ($[0] !== a) {
45 + t1 = () => [a, a.b.c];
46 + $[0] = a;
47 + $[1] = t1;
48 + } else {
49 + t1 = $[1];
50 + }
51 + const fn = t1;
52 + useIdentity(null);
53 + let x;
54 + if ($[2] !== cond || $[3] !== a.b.c) {
55 + x = makeArray();
56 + if (cond) {
57 + x.push(identity(a.b.c));
58 + }
59 + $[2] = cond;
60 + $[3] = a.b.c;
61 + $[4] = x;
62 + } else {
63 + x = $[4];
64 + }
65 + let t2;
66 + if ($[5] !== fn || $[6] !== x) {
67 + t2 = <Stringify fn={fn} x={x} shouldInvokeFns={true} />;
68 + $[5] = fn;
69 + $[6] = x;
70 + $[7] = t2;
71 + } else {
72 + t2 = $[7];
73 + }
74 + return t2;
75 +}
76 +
77 +export const FIXTURE_ENTRYPOINT = {
78 + fn: Foo,
79 + params: [{ a: null, cond: true }],
80 + sequentialRenders: [
81 + { a: null, cond: true },
82 + { a: { b: { c: 4 } }, cond: true },
83 + { a: { b: { c: 4 } }, cond: true },
84 + ],
85 +};
86 +
87 +```
88 +
89 +### Eval output
90 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
91 +<div>{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}</div>
92 +<div>{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.tsx new
+25
@@ -0,0 +1,25 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
4 +
5 +function Foo({a, cond}) {
6 + // Assume fn will be uncond evaluated, so we can safely evaluate {a.<any>,
7 + // a.b.<any}
8 + const fn = () => [a, a.b.c];
9 + useIdentity(null);
10 + const x = makeArray();
11 + if (cond) {
12 + x.push(identity(a.b.c));
13 + }
14 + return <Stringify fn={fn} x={x} shouldInvokeFns={true} />;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Foo,
19 + params: [{a: null, cond: true}],
20 + sequentialRenders: [
21 + {a: null, cond: true},
22 + {a: {b: {c: 4}}, cond: true},
23 + {a: {b: {c: 4}}, cond: true},
24 + ],
25 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-local-var.expect.md new
+73
@@ -0,0 +1,73 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {mutate, shallowCopy, Stringify} from 'shared-runtime';
8 +
9 +function useFoo({a}: {a: {b: {c: number}}}) {
10 + const local = shallowCopy(a);
11 + mutate(local);
12 + const fn = () => local.b.c;
13 + return <Stringify fn={fn} shouldInvokeFns={true} />;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [{a: null}],
19 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
28 +
29 +import { mutate, shallowCopy, Stringify } from "shared-runtime";
30 +
31 +function useFoo(t0) {
32 + const $ = _c(6);
33 + const { a } = t0;
34 + let local;
35 + if ($[0] !== a) {
36 + local = shallowCopy(a);
37 + mutate(local);
38 + $[0] = a;
39 + $[1] = local;
40 + } else {
41 + local = $[1];
42 + }
43 + let t1;
44 + if ($[2] !== local.b.c) {
45 + t1 = () => local.b.c;
46 + $[2] = local.b.c;
47 + $[3] = t1;
48 + } else {
49 + t1 = $[3];
50 + }
51 + const fn = t1;
52 + let t2;
53 + if ($[4] !== fn) {
54 + t2 = <Stringify fn={fn} shouldInvokeFns={true} />;
55 + $[4] = fn;
56 + $[5] = t2;
57 + } else {
58 + t2 = $[5];
59 + }
60 + return t2;
61 +}
62 +
63 +export const FIXTURE_ENTRYPOINT = {
64 + fn: useFoo,
65 + params: [{ a: null }],
66 + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
67 +};
68 +
69 +```
70 +
71 +### Eval output
72 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
73 +<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-local-var.tsx new
+16
@@ -0,0 +1,16 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {mutate, shallowCopy, Stringify} from 'shared-runtime';
4 +
5 +function useFoo({a}: {a: {b: {c: number}}}) {
6 + const local = shallowCopy(a);
7 + mutate(local);
8 + const fn = () => local.b.c;
9 + return <Stringify fn={fn} shouldInvokeFns={true} />;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useFoo,
14 + params: [{a: null}],
15 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
16 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md new
+91
@@ -0,0 +1,91 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
8 +
9 +function Foo({a, cond}) {
10 + // Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c.<any>
11 + const fn = () => [a, a.b?.c.d];
12 + useIdentity(null);
13 + const arr = makeArray();
14 + if (cond) {
15 + arr.push(identity(a.b?.c.e));
16 + }
17 + return <Stringify fn={fn} arr={arr} shouldInvokeFns={true} />;
18 +}
19 +
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: Foo,
22 + params: [{a: null, cond: true}],
23 + sequentialRenders: [
24 + {a: null, cond: true},
25 + {a: {b: {c: {d: 5}}}, cond: true},
26 + {a: {b: null}, cond: false},
27 + ],
28 +};
29 +
30 +```
31 +
32 +## Code
33 +
34 +```javascript
35 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
36 +
37 +import { identity, makeArray, Stringify, useIdentity } from "shared-runtime";
38 +
39 +function Foo(t0) {
40 + const $ = _c(8);
41 + const { a, cond } = t0;
42 + let t1;
43 + if ($[0] !== a) {
44 + t1 = () => [a, a.b?.c.d];
45 + $[0] = a;
46 + $[1] = t1;
47 + } else {
48 + t1 = $[1];
49 + }
50 + const fn = t1;
51 + useIdentity(null);
52 + let arr;
53 + if ($[2] !== cond || $[3] !== a.b?.c.e) {
54 + arr = makeArray();
55 + if (cond) {
56 + arr.push(identity(a.b?.c.e));
57 + }
58 + $[2] = cond;
59 + $[3] = a.b?.c.e;
60 + $[4] = arr;
61 + } else {
62 + arr = $[4];
63 + }
64 + let t2;
65 + if ($[5] !== fn || $[6] !== arr) {
66 + t2 = <Stringify fn={fn} arr={arr} shouldInvokeFns={true} />;
67 + $[5] = fn;
68 + $[6] = arr;
69 + $[7] = t2;
70 + } else {
71 + t2 = $[7];
72 + }
73 + return t2;
74 +}
75 +
76 +export const FIXTURE_ENTRYPOINT = {
77 + fn: Foo,
78 + params: [{ a: null, cond: true }],
79 + sequentialRenders: [
80 + { a: null, cond: true },
81 + { a: { b: { c: { d: 5 } } }, cond: true },
82 + { a: { b: null }, cond: false },
83 + ],
84 +};
85 +
86 +```
87 +
88 +### Eval output
89 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
90 +<div>{"fn":{"kind":"Function","result":[{"b":{"c":{"d":5}}},5]},"arr":[null],"shouldInvokeFns":true}</div>
91 +<div>{"fn":{"kind":"Function","result":[{"b":null},null]},"arr":[],"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.tsx new
+24
@@ -0,0 +1,24 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
4 +
5 +function Foo({a, cond}) {
6 + // Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c.<any>
7 + const fn = () => [a, a.b?.c.d];
8 + useIdentity(null);
9 + const arr = makeArray();
10 + if (cond) {
11 + arr.push(identity(a.b?.c.e));
12 + }
13 + return <Stringify fn={fn} arr={arr} shouldInvokeFns={true} />;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Foo,
18 + params: [{a: null, cond: true}],
19 + sequentialRenders: [
20 + {a: null, cond: true},
21 + {a: {b: {c: {d: 5}}}, cond: true},
22 + {a: {b: null}, cond: false},
23 + ],
24 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access-local-var.expect.md new
+73
@@ -0,0 +1,73 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {shallowCopy, Stringify, mutate} from 'shared-runtime';
8 +
9 +function useFoo({a}: {a: {b: {c: number}}}) {
10 + const local = shallowCopy(a);
11 + mutate(local);
12 + const fn = () => [() => local.b.c];
13 + return <Stringify fn={fn} shouldInvokeFns={true} />;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [{a: null}],
19 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
28 +
29 +import { shallowCopy, Stringify, mutate } from "shared-runtime";
30 +
31 +function useFoo(t0) {
32 + const $ = _c(6);
33 + const { a } = t0;
34 + let local;
35 + if ($[0] !== a) {
36 + local = shallowCopy(a);
37 + mutate(local);
38 + $[0] = a;
39 + $[1] = local;
40 + } else {
41 + local = $[1];
42 + }
43 + let t1;
44 + if ($[2] !== local.b.c) {
45 + t1 = () => [() => local.b.c];
46 + $[2] = local.b.c;
47 + $[3] = t1;
48 + } else {
49 + t1 = $[3];
50 + }
51 + const fn = t1;
52 + let t2;
53 + if ($[4] !== fn) {
54 + t2 = <Stringify fn={fn} shouldInvokeFns={true} />;
55 + $[4] = fn;
56 + $[5] = t2;
57 + } else {
58 + t2 = $[5];
59 + }
60 + return t2;
61 +}
62 +
63 +export const FIXTURE_ENTRYPOINT = {
64 + fn: useFoo,
65 + params: [{ a: null }],
66 + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
67 +};
68 +
69 +```
70 +
71 +### Eval output
72 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
73 +<div>{"fn":{"kind":"Function","result":[{"kind":"Function","result":4}]},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access-local-var.tsx new
+16
@@ -0,0 +1,16 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {shallowCopy, Stringify, mutate} from 'shared-runtime';
4 +
5 +function useFoo({a}: {a: {b: {c: number}}}) {
6 + const local = shallowCopy(a);
7 + mutate(local);
8 + const fn = () => [() => local.b.c];
9 + return <Stringify fn={fn} shouldInvokeFns={true} />;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useFoo,
14 + params: [{a: null}],
15 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
16 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.expect.md new
+66
@@ -0,0 +1,66 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {Stringify} from 'shared-runtime';
8 +
9 +function useFoo({a}) {
10 + const fn = () => {
11 + return () => ({
12 + value: a.b.c,
13 + });
14 + };
15 + return <Stringify fn={fn} shouldInvokeFns={true} />;
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useFoo,
20 + params: [{a: null}],
21 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
22 +};
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
30 +
31 +import { Stringify } from "shared-runtime";
32 +
33 +function useFoo(t0) {
34 + const $ = _c(4);
35 + const { a } = t0;
36 + let t1;
37 + if ($[0] !== a.b.c) {
38 + t1 = () => () => ({ value: a.b.c });
39 + $[0] = a.b.c;
40 + $[1] = t1;
41 + } else {
42 + t1 = $[1];
43 + }
44 + const fn = t1;
45 + let t2;
46 + if ($[2] !== fn) {
47 + t2 = <Stringify fn={fn} shouldInvokeFns={true} />;
48 + $[2] = fn;
49 + $[3] = t2;
50 + } else {
51 + t2 = $[3];
52 + }
53 + return t2;
54 +}
55 +
56 +export const FIXTURE_ENTRYPOINT = {
57 + fn: useFoo,
58 + params: [{ a: null }],
59 + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
60 +};
61 +
62 +```
63 +
64 +### Eval output
65 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
66 +<div>{"fn":{"kind":"Function","result":{"kind":"Function","result":{"value":4}}},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.tsx new
+18
@@ -0,0 +1,18 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {Stringify} from 'shared-runtime';
4 +
5 +function useFoo({a}) {
6 + const fn = () => {
7 + return () => ({
8 + value: a.b.c,
9 + });
10 + };
11 + return <Stringify fn={fn} shouldInvokeFns={true} />;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useFoo,
16 + params: [{a: null}],
17 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
18 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.expect.md new
+70
@@ -0,0 +1,70 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {identity, Stringify} from 'shared-runtime';
8 +
9 +function useFoo({a}) {
10 + const x = {
11 + fn() {
12 + return identity(a.b.c);
13 + },
14 + };
15 + return <Stringify x={x} shouldInvokeFns={true} />;
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useFoo,
20 + params: [{a: null}],
21 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
22 +};
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
30 +
31 +import { identity, Stringify } from "shared-runtime";
32 +
33 +function useFoo(t0) {
34 + const $ = _c(4);
35 + const { a } = t0;
36 + let t1;
37 + if ($[0] !== a.b.c) {
38 + t1 = {
39 + fn() {
40 + return identity(a.b.c);
41 + },
42 + };
43 + $[0] = a.b.c;
44 + $[1] = t1;
45 + } else {
46 + t1 = $[1];
47 + }
48 + const x = t1;
49 + let t2;
50 + if ($[2] !== x) {
51 + t2 = <Stringify x={x} shouldInvokeFns={true} />;
52 + $[2] = x;
53 + $[3] = t2;
54 + } else {
55 + t2 = $[3];
56 + }
57 + return t2;
58 +}
59 +
60 +export const FIXTURE_ENTRYPOINT = {
61 + fn: useFoo,
62 + params: [{ a: null }],
63 + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
64 +};
65 +
66 +```
67 +
68 +### Eval output
69 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
70 +<div>{"x":{"fn":{"kind":"Function","result":4}},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.tsx new
+18
@@ -0,0 +1,18 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {identity, Stringify} from 'shared-runtime';
4 +
5 +function useFoo({a}) {
6 + const x = {
7 + fn() {
8 + return identity(a.b.c);
9 + },
10 + };
11 + return <Stringify x={x} shouldInvokeFns={true} />;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useFoo,
16 + params: [{a: null}],
17 + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
18 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md new
+64
@@ -0,0 +1,64 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enablePropagateDepsInHIR
6 +
7 +import {Stringify} from 'shared-runtime';
8 +
9 +function useFoo({a}) {
10 + return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: useFoo,
15 + params: [{a: null}],
16 + sequentialRenders: [
17 + {a: null},
18 + {a: {b: null}},
19 + {a: {b: {c: {d: null}}}},
20 + {a: {b: {c: {d: {e: 4}}}}},
21 + ],
22 +};
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
30 +
31 +import { Stringify } from "shared-runtime";
32 +
33 +function useFoo(t0) {
34 + const $ = _c(2);
35 + const { a } = t0;
36 + let t1;
37 + if ($[0] !== a.b) {
38 + t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
39 + $[0] = a.b;
40 + $[1] = t1;
41 + } else {
42 + t1 = $[1];
43 + }
44 + return t1;
45 +}
46 +
47 +export const FIXTURE_ENTRYPOINT = {
48 + fn: useFoo,
49 + params: [{ a: null }],
50 + sequentialRenders: [
51 + { a: null },
52 + { a: { b: null } },
53 + { a: { b: { c: { d: null } } } },
54 + { a: { b: { c: { d: { e: 4 } } } } },
55 + ],
56 +};
57 +
58 +```
59 +
60 +### Eval output
61 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
62 +<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
63 +<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
64 +<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx new
+18
@@ -0,0 +1,18 @@
1 +// @enablePropagateDepsInHIR
2 +
3 +import {Stringify} from 'shared-runtime';
4 +
5 +function useFoo({a}) {
6 + return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: useFoo,
11 + params: [{a: null}],
12 + sequentialRenders: [
13 + {a: null},
14 + {a: {b: null}},
15 + {a: {b: {c: {d: null}}}},
16 + {a: {b: {c: {d: {e: 4}}}}},
17 + ],
18 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md new
+73
@@ -0,0 +1,73 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {Stringify} from 'shared-runtime';
6 +
7 +function Foo({a, shouldReadA}) {
8 + return (
9 + <Stringify
10 + fn={() => {
11 + if (shouldReadA) return a.b.c;
12 + return null;
13 + }}
14 + shouldInvokeFns={true}
15 + />
16 + );
17 +}
18 +
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: Foo,
21 + params: [{a: null, shouldReadA: true}],
22 + sequentialRenders: [
23 + {a: null, shouldReadA: true},
24 + {a: null, shouldReadA: false},
25 + {a: {b: {c: 4}}, shouldReadA: true},
26 + ],
27 +};
28 +
29 +```
30 +
31 +## Code
32 +
33 +```javascript
34 +import { c as _c } from "react/compiler-runtime";
35 +import { Stringify } from "shared-runtime";
36 +
37 +function Foo(t0) {
38 + const $ = _c(3);
39 + const { a, shouldReadA } = t0;
40 + let t1;
41 + if ($[0] !== shouldReadA || $[1] !== a.b.c) {
42 + t1 = (
43 + <Stringify
44 + fn={() => {
45 + if (shouldReadA) {
46 + return a.b.c;
47 + }
48 + return null;
49 + }}
50 + shouldInvokeFns={true}
51 + />
52 + );
53 + $[0] = shouldReadA;
54 + $[1] = a.b.c;
55 + $[2] = t1;
56 + } else {
57 + t1 = $[2];
58 + }
59 + return t1;
60 +}
61 +
62 +export const FIXTURE_ENTRYPOINT = {
63 + fn: Foo,
64 + params: [{ a: null, shouldReadA: true }],
65 + sequentialRenders: [
66 + { a: null, shouldReadA: true },
67 + { a: null, shouldReadA: false },
68 + { a: { b: { c: 4 } }, shouldReadA: true },
69 + ],
70 +};
71 +
72 +```
73 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.tsx new
+23
@@ -0,0 +1,23 @@
1 +import {Stringify} from 'shared-runtime';
2 +
3 +function Foo({a, shouldReadA}) {
4 + return (
5 + <Stringify
6 + fn={() => {
7 + if (shouldReadA) return a.b.c;
8 + return null;
9 + }}
10 + shouldInvokeFns={true}
11 + />
12 + );
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Foo,
17 + params: [{a: null, shouldReadA: true}],
18 + sequentialRenders: [
19 + {a: null, shouldReadA: true},
20 + {a: null, shouldReadA: false},
21 + {a: {b: {c: 4}}, shouldReadA: true},
22 + ],
23 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md new
+61
@@ -0,0 +1,61 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {Stringify} from 'shared-runtime';
6 +
7 +function useFoo({a}) {
8 + return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: useFoo,
13 + params: [{a: null}],
14 + sequentialRenders: [
15 + {a: null},
16 + {a: {b: null}},
17 + {a: {b: {c: {d: null}}}},
18 + {a: {b: {c: {d: {e: 4}}}}},
19 + ],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime";
28 +import { Stringify } from "shared-runtime";
29 +
30 +function useFoo(t0) {
31 + const $ = _c(2);
32 + const { a } = t0;
33 + let t1;
34 + if ($[0] !== a.b) {
35 + t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
36 + $[0] = a.b;
37 + $[1] = t1;
38 + } else {
39 + t1 = $[1];
40 + }
41 + return t1;
42 +}
43 +
44 +export const FIXTURE_ENTRYPOINT = {
45 + fn: useFoo,
46 + params: [{ a: null }],
47 + sequentialRenders: [
48 + { a: null },
49 + { a: { b: null } },
50 + { a: { b: { c: { d: null } } } },
51 + { a: { b: { c: { d: { e: 4 } } } } },
52 + ],
53 +};
54 +
55 +```
56 +
57 +### Eval output
58 +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
59 +<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
60 +<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
61 +<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx new
+16
@@ -0,0 +1,16 @@
1 +import {Stringify} from 'shared-runtime';
2 +
3 +function useFoo({a}) {
4 + return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
5 +}
6 +
7 +export const FIXTURE_ENTRYPOINT = {
8 + fn: useFoo,
9 + params: [{a: null}],
10 + sequentialRenders: [
11 + {a: null},
12 + {a: {b: null}},
13 + {a: {b: {c: {d: null}}}},
14 + {a: {b: {c: {d: {e: 4}}}}},
15 + ],
16 +};
compiler/packages/snap/src/SproutTodoFilter.ts
+1
@@ -479,6 +479,7 @@ const skipFilter = new Set([
479 'fbt/bug-fbt-plural-multiple-mixed-call-tag',
480 'bug-invalid-hoisting-functionexpr',
481 'bug-try-catch-maybe-null-dependency',
482 + 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted',
483 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond',
484 'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block',
485 'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',