diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d01fa3e2280d1..ae15c0abbefac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -7,6 +7,7 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; +import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -15,10 +16,12 @@ import { HIRFunction, Identifier, IdentifierId, + InstructionId, InstructionValue, ReactiveScopeDependency, ScopeId, } from './HIR'; +import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph @@ -83,28 +86,57 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, -): ReadonlyMap { + nestedFnImmutableContext: ReadonlySet | null, +): ReadonlyMap { const registry = new PropertyPathRegistry(); - const nodes = collectNonNullsInBlocks( - fn, - temporaries, + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + /** + * Due to current limitations of mutable range inference, there are edge cases in + * which we infer known-immutable values (e.g. props or hook params) to have a + * mutable range and scope. + * (see `destructure-array-declaration-to-context-var` fixture) + * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps + * is being rewritten to HIR). + */ + const knownImmutableIdentifiers = new Set(); + if (fn.fnType === 'Component' || fn.fnType === 'Hook') { + for (const p of fn.params) { + if (p.kind === 'Identifier') { + knownImmutableIdentifiers.add(p.identifier.id); + } + } + } + const nodes = collectNonNullsInBlocks(fn, { + temporaries: actuallyEvaluatedTemporaries, + knownImmutableIdentifiers, hoistableFromOptionals, registry, - ); + nestedFnImmutableContext, + }); propagateNonNull(fn, nodes, registry); - const nodesKeyedByScopeId = new Map(); + return nodes; +} + +export function keyByScopeId( + fn: HIRFunction, + source: ReadonlyMap, +): ReadonlyMap { + const keyedByScopeId = new Map(); for (const [_, block] of fn.body.blocks) { if (block.terminal.kind === 'scope') { - nodesKeyedByScopeId.set( + keyedByScopeId.set( block.terminal.scope.id, - nodes.get(block.terminal.block)!, + source.get(block.terminal.block)!, ); } } - - return nodesKeyedByScopeId; + return keyedByScopeId; } export type BlockInfo = { @@ -211,45 +243,75 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - temporaries: ReadonlyMap, - registry: PropertyPathRegistry, + context: CollectNonNullsInBlocksContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { - path = temporaries.get(instr.object.identifier.id) ?? { + path = context.temporaries.get(instr.object.identifier.id) ?? { identifier: instr.object.identifier, path: [], }; } else if (instr.kind === 'Destructure') { - path = temporaries.get(instr.value.identifier.id) ?? null; + path = context.temporaries.get(instr.value.identifier.id) ?? null; } else if (instr.kind === 'ComputedLoad') { - path = temporaries.get(instr.object.identifier.id) ?? null; + path = context.temporaries.get(instr.object.identifier.id) ?? null; + } + return path != null ? context.registry.getOrCreateProperty(path) : null; +} + +function isImmutableAtInstr( + identifier: Identifier, + instr: InstructionId, + context: CollectNonNullsInBlocksContext, +): boolean { + if (context.nestedFnImmutableContext != null) { + /** + * Comparing instructions ids across inner-outer function bodies is not valid, as they are numbered + */ + return context.nestedFnImmutableContext.has(identifier.id); + } else { + /** + * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges + * are not valid with respect to current instruction id numbering. + * We use attached reactive scope ranges as a proxy for mutable range, but this + * is an overestimate as (1) scope ranges merge and align to form valid program + * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to + * non-mutable identifiers. + * + * See comment in exported function for why we track known immutable identifiers. + */ + const mutableAtInstr = + identifier.mutableRange.end > identifier.mutableRange.start + 1 && + identifier.scope != null && + inRange( + { + id: instr, + }, + identifier.scope.range, + ); + return ( + !mutableAtInstr || context.knownImmutableIdentifiers.has(identifier.id) + ); } - return path != null ? registry.getOrCreateProperty(path) : null; } +type CollectNonNullsInBlocksContext = { + temporaries: ReadonlyMap; + knownImmutableIdentifiers: ReadonlySet; + hoistableFromOptionals: ReadonlyMap; + registry: PropertyPathRegistry; + /** + * (For nested / inner function declarations) + * Context variables (i.e. captured from an outer scope) that are immutable. + * Note that this technically could be merged into `knownImmutableIdentifiers`, + * but are currently kept separate for readability. + */ + nestedFnImmutableContext: ReadonlySet | null; +}; function collectNonNullsInBlocks( fn: HIRFunction, - temporaries: ReadonlyMap, - hoistableFromOptionals: ReadonlyMap, - registry: PropertyPathRegistry, + context: CollectNonNullsInBlocksContext, ): ReadonlyMap { - /** - * Due to current limitations of mutable range inference, there are edge cases in - * which we infer known-immutable values (e.g. props or hook params) to have a - * mutable range and scope. - * (see `destructure-array-declaration-to-context-var` fixture) - * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps - * is being rewritten to HIR). - */ - const knownImmutableIdentifiers = new Set(); - if (fn.fnType === 'Component' || fn.fnType === 'Hook') { - for (const p of fn.params) { - if (p.kind === 'Identifier') { - knownImmutableIdentifiers.add(p.identifier.id); - } - } - } /** * Known non-null objects such as functional component props can be safely * read from any block. @@ -261,7 +323,9 @@ function collectNonNullsInBlocks( fn.params[0].kind === 'Identifier' ) { const identifier = fn.params[0].identifier; - knownNonNullIdentifiers.add(registry.getOrCreateIdentifier(identifier)); + knownNonNullIdentifiers.add( + context.registry.getOrCreateIdentifier(identifier), + ); } const nodes = new Map(); for (const [_, block] of fn.body.blocks) { @@ -273,46 +337,49 @@ function collectNonNullsInBlocks( block, assumedNonNullObjects, }); - const maybeOptionalChain = hoistableFromOptionals.get(block.id); + const maybeOptionalChain = context.hoistableFromOptionals.get(block.id); if (maybeOptionalChain != null) { assumedNonNullObjects.add( - registry.getOrCreateProperty(maybeOptionalChain), + context.registry.getOrCreateProperty(maybeOptionalChain), ); continue; } for (const instr of block.instructions) { - const maybeNonNull = getMaybeNonNullInInstruction( - instr.value, - temporaries, - registry, - ); - if (maybeNonNull != null) { - const baseIdentifier = maybeNonNull.fullPath.identifier; - /** - * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges - * are not valid with respect to current instruction id numbering. - * We use attached reactive scope ranges as a proxy for mutable range, but this - * is an overestimate as (1) scope ranges merge and align to form valid program - * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to - * non-mutable identifiers. - * - * See comment at top of function for why we track known immutable identifiers. - */ - const isMutableAtInstr = - baseIdentifier.mutableRange.end > - baseIdentifier.mutableRange.start + 1 && - baseIdentifier.scope != null && - inRange( - { - id: instr.id, - }, - baseIdentifier.scope.range, - ); - if ( - !isMutableAtInstr || - knownImmutableIdentifiers.has(baseIdentifier.id) - ) { - assumedNonNullObjects.add(maybeNonNull); + const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context); + if ( + maybeNonNull != null && + isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context) + ) { + assumedNonNullObjects.add(maybeNonNull); + } + if ( + instr.value.kind === 'FunctionExpression' && + !fn.env.config.enableTreatFunctionDepsAsConditional + ) { + const innerFn = instr.value.loweredFunc; + const innerTemporaries = collectTemporariesSidemap( + innerFn.func, + new Set(), + ); + const innerOptionals = collectOptionalChainSidemap(innerFn.func); + const innerHoistableMap = collectHoistablePropertyLoads( + innerFn.func, + innerTemporaries, + innerOptionals.hoistableObjects, + context.nestedFnImmutableContext ?? + new Set( + innerFn.func.context + .filter(place => + isImmutableAtInstr(place.identifier, instr.id, context), + ) + .map(place => place.identifier.id), + ), + ); + const innerHoistables = assertNonNull( + innerHoistableMap.get(innerFn.func.body.entry), + ); + for (const entry of innerHoistables.assumedNonNullObjects) { + assumedNonNullObjects.add(entry); } } } @@ -515,3 +582,27 @@ function reduceMaybeOptionalChains( } } while (changed); } + +function collectFunctionExpressionFakeLoads( + fn: HIRFunction, +): Set { + const sources = new Map(); + const functionExpressionReferences = new Set(); + + for (const [_, block] of fn.body.blocks) { + for (const {lvalue, value} of block.instructions) { + if (value.kind === 'FunctionExpression') { + for (const reference of value.loweredFunc.dependencies) { + let curr: IdentifierId | undefined = reference.identifier.id; + while (curr != null) { + functionExpressionReferences.add(curr); + curr = sources.get(curr); + } + } + } else if (value.kind === 'PropertyLoad') { + sources.set(lvalue.identifier.id, value.object.identifier.id); + } + } + } + return functionExpressionReferences; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index a7346e0e6b984..ab2cf4cf56157 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -17,7 +17,10 @@ import { areEqualPaths, IdentifierId, } from './HIR'; -import {collectHoistablePropertyLoads} from './CollectHoistablePropertyLoads'; +import { + collectHoistablePropertyLoads, + keyByScopeId, +} from './CollectHoistablePropertyLoads'; import { ScopeBlockTraversal, eachInstructionOperand, @@ -41,10 +44,9 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { hoistableObjects, } = collectOptionalChainSidemap(fn); - const hoistablePropertyLoads = collectHoistablePropertyLoads( + const hoistablePropertyLoads = keyByScopeId( fn, - temporaries, - hoistableObjects, + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), ); const scopeDeps = collectDependencies( @@ -209,7 +211,7 @@ function findTemporariesUsedOutsideDeclaringScope( * of $1, as the evaluation of `arr.length` changes between instructions $1 and * $3. We do not track $1 -> arr.length in this case. */ -function collectTemporariesSidemap( +export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { diff --git a/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 b/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 file mode 100644 index 0000000000000..c0f8aa97cd47e --- /dev/null +++ b/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 @@ -0,0 +1,97 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {shallowCopy, mutate, Stringify} from 'shared-runtime'; + +function useFoo({ + a, + shouldReadA, +}: { + a: {b: {c: number}; x: number}; + shouldReadA: boolean; +}) { + const local = shallowCopy(a); + mutate(local); + return ( + { + if (shouldReadA) return local.b.c; + return null; + }} + shouldInvokeFns={true} + /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { shallowCopy, mutate, Stringify } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(5); + const { a, shouldReadA } = t0; + let local; + if ($[0] !== a) { + local = shallowCopy(a); + mutate(local); + $[0] = a; + $[1] = local; + } else { + local = $[1]; + } + let t1; + if ($[2] !== shouldReadA || $[3] !== local) { + t1 = ( + { + if (shouldReadA) { + return local.b.c; + } + return null; + }} + shouldInvokeFns={true} + /> + ); + $[2] = shouldReadA; + $[3] = local; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +
{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..fdf22dc970b15 --- /dev/null +++ b/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 @@ -0,0 +1,33 @@ +// @enablePropagateDepsInHIR + +import {shallowCopy, mutate, Stringify} from 'shared-runtime'; + +function useFoo({ + a, + shouldReadA, +}: { + a: {b: {c: number}; x: number}; + shouldReadA: boolean; +}) { + const local = shallowCopy(a); + mutate(local); + return ( + { + if (shouldReadA) return local.b.c; + return null; + }} + shouldInvokeFns={true} + /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..e37b8365a2faf --- /dev/null +++ b/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 @@ -0,0 +1,80 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + { + if (shouldReadA) return a.b.c; + return null; + }} + shouldInvokeFns={true} + /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(3); + const { a, shouldReadA } = t0; + let t1; + if ($[0] !== shouldReadA || $[1] !== a) { + t1 = ( + { + if (shouldReadA) { + return a.b.c; + } + return null; + }} + shouldInvokeFns={true} + /> + ); + $[0] = shouldReadA; + $[1] = a; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..5c71d57750c38 --- /dev/null +++ b/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 @@ -0,0 +1,25 @@ +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + { + if (shouldReadA) return a.b.c; + return null; + }} + shouldInvokeFns={true} + /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..395d9e1fdfe52 --- /dev/null +++ b/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 @@ -0,0 +1,51 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b.c} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { Stringify } from "shared-runtime"; + +function useFoo(a) { + const $ = _c(2); + let t0; + if ($[0] !== a.b.c) { + t0 = a.b.c} shouldInvokeFns={true} />; + $[0] = a.b.c; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.tsx b/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 file mode 100644 index 0000000000000..a02139c409bfe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-function-uncond-access-hoisted.tsx @@ -0,0 +1,13 @@ +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b.c} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..89b4d281f86dc --- /dev/null +++ b/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 @@ -0,0 +1,92 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn will be uncond evaluated, so we can safely evaluate {a., + // a.b. [a, a.b.c]; + useIdentity(null); + const x = makeArray(); + if (cond) { + x.push(identity(a.b.c)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: 4}}, cond: true}, + {a: {b: {c: 4}}, cond: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { identity, makeArray, Stringify, useIdentity } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(8); + const { a, cond } = t0; + let t1; + if ($[0] !== a) { + t1 = () => [a, a.b.c]; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const fn = t1; + useIdentity(null); + let x; + if ($[2] !== cond || $[3] !== a.b.c) { + x = makeArray(); + if (cond) { + x.push(identity(a.b.c)); + } + $[2] = cond; + $[3] = a.b.c; + $[4] = x; + } else { + x = $[4]; + } + let t2; + if ($[5] !== fn || $[6] !== x) { + t2 = ; + $[5] = fn; + $[6] = x; + $[7] = t2; + } else { + t2 = $[7]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, cond: true }], + sequentialRenders: [ + { a: null, cond: true }, + { a: { b: { c: 4 } }, cond: true }, + { a: { b: { c: 4 } }, cond: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..a9956ed8a5ffe --- /dev/null +++ b/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 @@ -0,0 +1,25 @@ +// @enablePropagateDepsInHIR + +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn will be uncond evaluated, so we can safely evaluate {a., + // a.b. [a, a.b.c]; + useIdentity(null); + const x = makeArray(); + if (cond) { + x.push(identity(a.b.c)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: 4}}, cond: true}, + {a: {b: {c: 4}}, cond: true}, + ], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..741a30d7de24d --- /dev/null +++ b/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 @@ -0,0 +1,73 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {mutate, shallowCopy, Stringify} from 'shared-runtime'; + +function useFoo({a}: {a: {b: {c: number}}}) { + const local = shallowCopy(a); + mutate(local); + const fn = () => local.b.c; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { mutate, shallowCopy, Stringify } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(6); + const { a } = t0; + let local; + if ($[0] !== a) { + local = shallowCopy(a); + mutate(local); + $[0] = a; + $[1] = local; + } else { + local = $[1]; + } + let t1; + if ($[2] !== local.b.c) { + t1 = () => local.b.c; + $[2] = local.b.c; + $[3] = t1; + } else { + t1 = $[3]; + } + const fn = t1; + let t2; + if ($[4] !== fn) { + t2 = ; + $[4] = fn; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +
{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..16a0964351d0c --- /dev/null +++ b/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 @@ -0,0 +1,16 @@ +// @enablePropagateDepsInHIR + +import {mutate, shallowCopy, Stringify} from 'shared-runtime'; + +function useFoo({a}: {a: {b: {c: number}}}) { + const local = shallowCopy(a); + mutate(local); + const fn = () => local.b.c; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..591e04de7ba15 --- /dev/null +++ b/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 @@ -0,0 +1,91 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c. + const fn = () => [a, a.b?.c.d]; + useIdentity(null); + const arr = makeArray(); + if (cond) { + arr.push(identity(a.b?.c.e)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: {d: 5}}}, cond: true}, + {a: {b: null}, cond: false}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { identity, makeArray, Stringify, useIdentity } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(8); + const { a, cond } = t0; + let t1; + if ($[0] !== a) { + t1 = () => [a, a.b?.c.d]; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const fn = t1; + useIdentity(null); + let arr; + if ($[2] !== cond || $[3] !== a.b?.c.e) { + arr = makeArray(); + if (cond) { + arr.push(identity(a.b?.c.e)); + } + $[2] = cond; + $[3] = a.b?.c.e; + $[4] = arr; + } else { + arr = $[4]; + } + let t2; + if ($[5] !== fn || $[6] !== arr) { + t2 = ; + $[5] = fn; + $[6] = arr; + $[7] = t2; + } else { + t2 = $[7]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, cond: true }], + sequentialRenders: [ + { a: null, cond: true }, + { a: { b: { c: { d: 5 } } }, cond: true }, + { a: { b: null }, cond: false }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"fn":{"kind":"Function","result":[{"b":{"c":{"d":5}}},5]},"arr":[null],"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":[{"b":null},null]},"arr":[],"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..3b538de991dc0 --- /dev/null +++ b/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 @@ -0,0 +1,24 @@ +// @enablePropagateDepsInHIR + +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c. + const fn = () => [a, a.b?.c.d]; + useIdentity(null); + const arr = makeArray(); + if (cond) { + arr.push(identity(a.b?.c.e)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: {d: 5}}}, cond: true}, + {a: {b: null}, cond: false}, + ], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..ca65ce72bc172 --- /dev/null +++ b/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 @@ -0,0 +1,73 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {shallowCopy, Stringify, mutate} from 'shared-runtime'; + +function useFoo({a}: {a: {b: {c: number}}}) { + const local = shallowCopy(a); + mutate(local); + const fn = () => [() => local.b.c]; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { shallowCopy, Stringify, mutate } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(6); + const { a } = t0; + let local; + if ($[0] !== a) { + local = shallowCopy(a); + mutate(local); + $[0] = a; + $[1] = local; + } else { + local = $[1]; + } + let t1; + if ($[2] !== local.b.c) { + t1 = () => [() => local.b.c]; + $[2] = local.b.c; + $[3] = t1; + } else { + t1 = $[3]; + } + const fn = t1; + let t2; + if ($[4] !== fn) { + t2 = ; + $[4] = fn; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +
{"fn":{"kind":"Function","result":[{"kind":"Function","result":4}]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..d351a19464bf5 --- /dev/null +++ b/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 @@ -0,0 +1,16 @@ +// @enablePropagateDepsInHIR + +import {shallowCopy, Stringify, mutate} from 'shared-runtime'; + +function useFoo({a}: {a: {b: {c: number}}}) { + const local = shallowCopy(a); + mutate(local); + const fn = () => [() => local.b.c]; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..421f7a21305e9 --- /dev/null +++ b/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 @@ -0,0 +1,65 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + const fn = () => { + return () => ({ + value: a.b.c, + }); + }; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { Stringify } from "shared-runtime"; + +function useFoo(a) { + const $ = _c(4); + let t0; + if ($[0] !== a.b.c) { + t0 = () => () => ({ value: a.b.c }); + $[0] = a.b.c; + $[1] = t0; + } else { + t0 = $[1]; + } + const fn = t0; + let t1; + if ($[2] !== fn) { + t1 = ; + $[2] = fn; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.tsx b/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 file mode 100644 index 0000000000000..1c711c053861a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-nested-function-uncond-access.tsx @@ -0,0 +1,18 @@ +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + const fn = () => { + return () => ({ + value: a.b.c, + }); + }; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..9685e0c87d83d --- /dev/null +++ b/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 @@ -0,0 +1,69 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {identity, Stringify} from 'shared-runtime'; + +function useFoo(a) { + const x = { + fn() { + return identity(a.b.c); + }, + }; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { identity, Stringify } from "shared-runtime"; + +function useFoo(a) { + const $ = _c(4); + let t0; + if ($[0] !== a.b.c) { + t0 = { + fn() { + return identity(a.b.c); + }, + }; + $[0] = a.b.c; + $[1] = t0; + } else { + t0 = $[1]; + } + const x = t0; + let t1; + if ($[2] !== x) { + t1 = ; + $[2] = x; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.tsx b/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 file mode 100644 index 0000000000000..812de3dde9d6e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-object-method-uncond-access.tsx @@ -0,0 +1,18 @@ +// @enablePropagateDepsInHIR + +import {identity, Stringify} from 'shared-runtime'; + +function useFoo(a) { + const x = { + fn() { + return identity(a.b.c); + }, + }; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; diff --git a/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 b/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 file mode 100644 index 0000000000000..958f97d7331d5 --- /dev/null +++ b/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 @@ -0,0 +1,67 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b?.c.d?.e} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [ + {a: null}, + {a: {b: null}}, + {a: {b: {c: {d: null}}}}, + , + {a: {b: {c: {d: {e: 4}}}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR + +import { Stringify } from "shared-runtime"; + +function useFoo(a) { + const $ = _c(2); + let t0; + if ($[0] !== a.b) { + t0 = a.b?.c.d?.e} shouldInvokeFns={true} />; + $[0] = a.b; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [ + { a: null }, + { a: { b: null } }, + { a: { b: { c: { d: null } } } }, + + , + { a: { b: { c: { d: { e: 4 } } } } }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
+[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]] +
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/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 b/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 file mode 100644 index 0000000000000..79be2ba6bd740 --- /dev/null +++ b/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 @@ -0,0 +1,19 @@ +// @enablePropagateDepsInHIR + +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b?.c.d?.e} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [ + {a: null}, + {a: {b: null}}, + {a: {b: {c: {d: null}}}}, + , + {a: {b: {c: {d: {e: 4}}}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md new file mode 100644 index 0000000000000..4d45d3f3c6da6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.expect.md @@ -0,0 +1,73 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + { + if (shouldReadA) return a.b.c; + return null; + }} + shouldInvokeFns={true} + /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(3); + const { a, shouldReadA } = t0; + let t1; + if ($[0] !== shouldReadA || $[1] !== a.b.c) { + t1 = ( + { + if (shouldReadA) { + return a.b.c; + } + return null; + }} + shouldInvokeFns={true} + /> + ); + $[0] = shouldReadA; + $[1] = a.b.c; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, shouldReadA: true }], + sequentialRenders: [ + { a: null, shouldReadA: true }, + { a: null, shouldReadA: false }, + { a: { b: { c: 4 } }, shouldReadA: true }, + ], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.tsx new file mode 100644 index 0000000000000..e571ee7b953ec --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted.tsx @@ -0,0 +1,23 @@ +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + { + if (shouldReadA) return a.b.c; + return null; + }} + shouldInvokeFns={true} + /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, shouldReadA: true}], + sequentialRenders: [ + {a: null, shouldReadA: true}, + {a: null, shouldReadA: false}, + {a: {b: {c: 4}}, shouldReadA: true}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md new file mode 100644 index 0000000000000..ec9b504e8d899 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoisted.expect.md @@ -0,0 +1,48 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b.c} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function useFoo(a) { + const $ = _c(2); + let t0; + if ($[0] !== a.b.c) { + t0 = a.b.c} shouldInvokeFns={true} />; + $[0] = a.b.c; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoisted.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoisted.tsx new file mode 100644 index 0000000000000..4895a7002a135 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoisted.tsx @@ -0,0 +1,11 @@ +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b.c} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [{a: null}, {a: {b: {c: 4}}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md new file mode 100644 index 0000000000000..dd5f2c145ed2f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.expect.md @@ -0,0 +1,89 @@ + +## Input + +```javascript +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn will be uncond evaluated, so we can safely evaluate {a., + // a.b. [a, a.b.c]; + useIdentity(null); + const x = makeArray(); + if (cond) { + x.push(identity(a.b.c)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: 4}}, cond: true}, + {a: {b: {c: 4}}, cond: true}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, makeArray, Stringify, useIdentity } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(8); + const { a, cond } = t0; + let t1; + if ($[0] !== a) { + t1 = () => [a, a.b.c]; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const fn = t1; + useIdentity(null); + let x; + if ($[2] !== cond || $[3] !== a) { + x = makeArray(); + if (cond) { + x.push(identity(a.b.c)); + } + $[2] = cond; + $[3] = a; + $[4] = x; + } else { + x = $[4]; + } + let t2; + if ($[5] !== fn || $[6] !== x) { + t2 = ; + $[5] = fn; + $[6] = x; + $[7] = t2; + } else { + t2 = $[7]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, cond: true }], + sequentialRenders: [ + { a: null, cond: true }, + { a: { b: { c: 4 } }, cond: true }, + { a: { b: { c: 4 } }, cond: true }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.tsx new file mode 100644 index 0000000000000..fdc4babc6d00f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-access-hoists-other-dep.tsx @@ -0,0 +1,23 @@ +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn will be uncond evaluated, so we can safely evaluate {a., + // a.b. [a, a.b.c]; + useIdentity(null); + const x = makeArray(); + if (cond) { + x.push(identity(a.b.c)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: 4}}, cond: true}, + {a: {b: {c: 4}}, cond: true}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md new file mode 100644 index 0000000000000..bc4d0fc3df360 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.expect.md @@ -0,0 +1,88 @@ + +## Input + +```javascript +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c. + const fn = () => [a, a.b?.c.d]; + useIdentity(null); + const arr = makeArray(); + if (cond) { + arr.push(identity(a.b?.c.e)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: {d: 5}}}, cond: true}, + {a: {b: null}, cond: false}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, makeArray, Stringify, useIdentity } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(8); + const { a, cond } = t0; + let t1; + if ($[0] !== a) { + t1 = () => [a, a.b?.c.d]; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + const fn = t1; + useIdentity(null); + let arr; + if ($[2] !== cond || $[3] !== a) { + arr = makeArray(); + if (cond) { + arr.push(identity(a.b?.c.e)); + } + $[2] = cond; + $[3] = a; + $[4] = arr; + } else { + arr = $[4]; + } + let t2; + if ($[5] !== fn || $[6] !== arr) { + t2 = ; + $[5] = fn; + $[6] = arr; + $[7] = t2; + } else { + t2 = $[7]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ a: null, cond: true }], + sequentialRenders: [ + { a: null, cond: true }, + { a: { b: { c: { d: 5 } } }, cond: true }, + { a: { b: null }, cond: false }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]] +
{"fn":{"kind":"Function","result":[{"b":{"c":{"d":5}}},5]},"arr":[null],"shouldInvokeFns":true}
+
{"fn":{"kind":"Function","result":[{"b":null},null]},"arr":[],"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.tsx new file mode 100644 index 0000000000000..7facd985394bc --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/infer-function-uncond-optional-hoists-other-dep.tsx @@ -0,0 +1,22 @@ +import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({a, cond}) { + // Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c. + const fn = () => [a, a.b?.c.d]; + useIdentity(null); + const arr = makeArray(); + if (cond) { + arr.push(identity(a.b?.c.e)); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{a: null, cond: true}], + sequentialRenders: [ + {a: null, cond: true}, + {a: {b: {c: {d: 5}}}, cond: true}, + {a: {b: null}, cond: false}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md new file mode 100644 index 0000000000000..499abf992c146 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b?.c.d?.e} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [ + {a: null}, + {a: {b: null}}, + {a: {b: {c: {d: null}}}}, + , + {a: {b: {c: {d: {e: 4}}}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function useFoo(a) { + const $ = _c(2); + let t0; + if ($[0] !== a.b) { + t0 = a.b?.c.d?.e} shouldInvokeFns={true} />; + $[0] = a.b; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: null }], + sequentialRenders: [ + { a: null }, + { a: { b: null } }, + { a: { b: { c: { d: null } } } }, + + , + { a: { b: { c: { d: { e: 4 } } } } }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
+
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
+[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]] +
{"fn":{"kind":"Function"},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx new file mode 100644 index 0000000000000..076be2089ac86 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx @@ -0,0 +1,17 @@ +import {Stringify} from 'shared-runtime'; + +function useFoo(a) { + return a.b?.c.d?.e} shouldInvokeFns={true} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: null}], + sequentialRenders: [ + {a: null}, + {a: {b: null}}, + {a: {b: {c: {d: null}}}}, + , + {a: {b: {c: {d: {e: 4}}}}}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index c40392884d579..8597f66dbd788 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -479,6 +479,7 @@ const skipFilter = new Set([ 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-invalid-hoisting-functionexpr', 'bug-try-catch-maybe-null-dependency', + 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond', 'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block', 'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',