From 9f35794e90bd461fc337c5f2d22cd840dde8f9f8 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 9 Oct 2024 13:38:40 -0400 Subject: [PATCH 01/63] [compiler][ez] Patch hoistability for ObjectMethods --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { From 1c8e94435850c4c1d1648250167a48f84d6c39fd Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 9 Oct 2024 13:43:31 -0400 Subject: [PATCH 02/63] [compiler][waittocommit] default to enablePropagateScopeDepsHIR --- .../src/HIR/Environment.ts | 2 +- ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +++- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +++-- .../conditional-early-return.expect.md | 78 ++++++++++++------- .../compiler/conditional-on-mutable.expect.md | 24 +++--- ...rly-return-within-reactive-scope.expect.md | 26 ++++--- ...rly-return-within-reactive-scope.expect.md | 20 ++--- ...ession-with-conditional-optional.expect.md | 50 ++++++++++++ ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 ++++++++++++ ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +-- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +-- ...consequent-alternate-both-return.expect.md | 11 +-- ...ession-with-conditional-optional.expect.md | 74 ------------------ ...mber-expression-with-conditional.expect.md | 74 ------------------ ...rly-return-within-reactive-scope.expect.md | 22 +++--- ...ence-array-push-consecutive-phis.expect.md | 18 +++-- .../phi-type-inference-array-push.expect.md | 11 +-- ...hi-type-inference-property-store.expect.md | 11 +-- ...ack-conditional-access-own-scope.expect.md | 50 ++++++++++++ ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 ++++++++++++++ ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 -------------- ...ck-infer-conditional-value-block.expect.md | 63 --------------- ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +--- .../promote-uncond.expect.md | 13 ++-- .../ssa-cascading-eliminated-phis.expect.md | 25 ++++-- .../compiler/ssa-leave-case.expect.md | 11 +-- ...ernary-destruction-with-mutation.expect.md | 12 +-- ...ssa-renaming-ternary-destruction.expect.md | 11 +-- ...a-renaming-ternary-with-mutation.expect.md | 12 +-- .../compiler/ssa-renaming-ternary.expect.md | 11 +-- ...onditional-ternary-with-mutation.expect.md | 12 +-- ...a-renaming-unconditional-ternary.expect.md | 12 +-- ...ming-unconditional-with-mutation.expect.md | 12 +-- ...-via-destructuring-with-mutation.expect.md | 12 +-- .../ssa-renaming-with-mutation.expect.md | 12 +-- .../switch-non-final-default.expect.md | 31 ++++---- .../fixtures/compiler/switch.expect.md | 26 ++++--- .../try-catch-mutate-outer-value.expect.md | 16 +++- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 ++++-- 54 files changed, 554 insertions(+), 509 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b7d2b645c5..925991f690 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,7 +230,7 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), + enablePropagateDepsInHIR: z.boolean().default(true), /** * Enables inference of optional dependency chains. Without this flag diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; 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 index 4d45d3f3c6..352552bf02 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 6b10e016a257bf4fffbad671d016521366ed4447 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 11 Oct 2024 15:54:49 -0400 Subject: [PATCH 03/63] [compiler][waittocommit] Delete propagateScopeDeps (non-hir) --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 2 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- 8 files changed, 14 insertions(+), 1353 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 900e4f4908..f92849d075 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -343,14 +342,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -378,15 +375,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 925991f690..d228e93c51 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,8 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(true), - /** * Enables inference of optional dependency chains. Without this flag * a property chain such as `props?.items?.foo` will infer as a dep on diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; From af227d3e75cb2c5a8cdd23bcbb143042f2e30f82 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 9 Oct 2024 13:06:33 -0400 Subject: [PATCH 04/63] [compiler][needs cleanup]: stop using function deps in PropagateScopeDepsHIR --- .../src/HIR/CollectHoistablePropertyLoads.ts | 11 +- .../HIR/CollectOptionalChainDependencies.ts | 65 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 114 +++++++++++++++--- .../capturing-func-mutate-2.expect.md | 21 +--- .../object-shorthand-method-1.expect.md | 6 +- .../object-shorthand-method-nested.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++ ...k-captures-reassigned-context-property.tsx | 32 +++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------- ...k-captures-reassigned-context-property.tsx | 21 ---- ...back-captures-reassigned-context.expect.md | 16 ++- ...llback-extended-contextvar-scope.expect.md | 28 ++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 ++-- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 +++-- 17 files changed, 296 insertions(+), 222 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx 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 d2e7220159..0edec1d316 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -7,7 +7,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -21,7 +20,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph @@ -353,15 +351,10 @@ function collectNonNullsInBlocks( !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.temporaries, + context.hoistableFromOptionals, context.nestedFnImmutableContext ?? new Set( innerFn.func.context diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..5266dacca2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -97,7 +87,10 @@ export type OptionalChainSidemap = { * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +108,47 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + seenOptionals: new Set(), + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +393,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptional = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptional.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptional.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, 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 855ca9121d..2d5432d4b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -16,6 +16,8 @@ import { DeclarationId, areEqualPaths, IdentifierId, + BasicBlock, + BlockId, } from './HIR'; import { collectHoistablePropertyLoads, @@ -37,7 +39,11 @@ import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const usedOutsideDeclaringScope = findTemporariesUsedOutsideDeclaringScope(fn); - const temporaries = collectTemporariesSidemap(fn, usedOutsideDeclaringScope); + const temporaries = collectTemporariesSidemap( + fn, + usedOutsideDeclaringScope, + new Map(), + ); const { temporariesReadInOptional, processedInstrsInOptional, @@ -214,8 +220,9 @@ function findTemporariesUsedOutsideDeclaringScope( export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean = false, ): ReadonlyMap { - const temporaries = new Map(); for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,23 +231,54 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (isInnerFn) { + const source = temporaries.get(value.object.identifier.id); + if (source) { + // only add inner function loads if their source is valid + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } + } else { + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemap( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } @@ -310,6 +348,8 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + innerFnContext: HIRFunction | null = null; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -374,6 +414,14 @@ class Context { // Checks if identifier is a valid dependency in the current scope #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { + if ( + this.innerFnContext != null && + !this.innerFnContext.context.some( + context => context.identifier.id === maybeDependency.identifier.id, + ) + ) { + return false; + } // ref.current access is not a valid dep if ( isUseRefType(maybeDependency.identifier) && @@ -575,7 +623,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -594,8 +645,13 @@ function collectDependencies( } const scopeTraversal = new ScopeBlockTraversal(); + // TODO: make this less hacky + const st: Array<[HIRFunction, BlockId, BasicBlock]> = [...fn.body.blocks].map( + ([id, block]) => [fn, id, block], + ); - for (const [blockId, block] of fn.body.blocks) { + while (st.length != 0) { + const [currFn, blockId, block] = st.shift()!; scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); if (scopeBlockInfo?.kind === 'begin') { @@ -604,6 +660,9 @@ function collectDependencies( context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); } + // TODO: make this less hacky + context.innerFnContext = currFn === fn ? null : currFn; + // Record referenced optional chains in phis for (const phi of block.phis) { for (const operand of phi.operands) { @@ -614,16 +673,37 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + /** + * Push the outermost nested fn context, as these are guaranteed to reference + * component / hook identifiers (i.e. eligible dependencies) + */ + const innerFn = instr.value.loweredFunc.func; + const outermostNestedFnContext = currFn === fn ? innerFn : currFn; + const x: Array<[HIRFunction, BlockId, BasicBlock]> = [ + ...innerFn.body.blocks.entries(), + ].map(([id, block]) => [outermostNestedFnContext, id, block]); + st.unshift(...x); + + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + } else if (!processedInstrsInOptional.get(currFn)?.has(instr.id)) { + // instruction ids are function-relative handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!processedInstrsInOptional.get(currFn)?.has(block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } } } + return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-1.expect.md index 6b9ca0c231..3ebde23e38 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-1.expect.md @@ -40,15 +40,15 @@ function useHook(t0) { t1 = $[1]; } let t2; - if ($[2] !== b || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== b) { t2 = { x: t1, y() { return [b]; }, }; - $[2] = b; - $[3] = t1; + $[2] = t1; + $[3] = b; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { 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 index 02e60eff91..ed56ff0681 100644 --- 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 @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } 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 index 157e2de81a..bb99a5d90f 100644 --- 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 @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 3556fcd3adeeaf5f1dc696f6c906d48f14b7a89d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 11 Oct 2024 14:18:37 -0400 Subject: [PATCH 05/63] [compiler] Lower JSXMemberExpression with LoadLocal --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From d2ed23a07f8ab19fcc5c30b5db1e6961c93baaa2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 11 Oct 2024 15:29:04 -0400 Subject: [PATCH 06/63] [compiler][be] Clean up nested function context in DCE --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 70fb3f68490a1b9f216ecdb66f01461a3abc0016 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 11 Oct 2024 15:33:31 -0400 Subject: [PATCH 07/63] [compiler][be] Patch test fixtures for evaluator --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From dc43aea2694047f9aa223539435027b059dedd7e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 11 Oct 2024 15:37:25 -0400 Subject: [PATCH 08/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 34 +--- .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 34 files changed, 109 insertions(+), 325 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 0edec1d316..101f315a9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -88,11 +88,6 @@ export function collectHoistablePropertyLoads( ): ReadonlyMap { const registry = new PropertyPathRegistry(); - 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 @@ -110,7 +105,7 @@ export function collectHoistablePropertyLoads( } } const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, @@ -576,30 +571,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index e771b386b3..a7fa0280d1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..fe98ee1bf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 2a45eaca9e638007837911fdb0580e0384c79a93 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 21 Oct 2024 16:30:57 -0700 Subject: [PATCH 09/63] [compiler][ez] Clean up pragma parsing for tests + playground Move environment config parsing for `inlineJsxTransform`, `lowerContextAccess`, and some dev-only options out of snap (test fixture). These should now be available for playground via `@inlineJsxTransform` and `lowerContextAccess`. Other small change: Changed zod fields from `nullish()` -> `nullable().default(null)`. [`nullish`](https://zod.dev/?id=nullish) fields accept `null | undefined` and default to `undefined`. We don't distinguish between null and undefined for any of these options, so let's only accept null + default to null. This also makes EnvironmentConfig in the playground more accurate. Previously, some fields just didn't show up as `prettyFormat({field: undefined})` does not print `field`. --- .../src/HIR/Environment.ts | 92 +++++++++++++------ ...codegen-emit-imports-same-source.expect.md | 4 +- .../codegen-emit-imports-same-source.js | 2 +- ...en-instrument-forget-gating-test.expect.md | 4 +- .../codegen-instrument-forget-gating-test.js | 2 +- .../codegen-instrument-forget-test.expect.md | 4 +- .../codegen-instrument-forget-test.js | 2 +- .../compiler/inline-jsx-transform.expect.md | 4 +- .../fixtures/compiler/inline-jsx-transform.js | 2 +- compiler/packages/snap/src/compiler.ts | 70 +++----------- 10 files changed, 87 insertions(+), 99 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..8c615119d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -68,8 +68,8 @@ export const ExternalFunctionSchema = z.object({ export const InstrumentationSchema = z .object({ fn: ExternalFunctionSchema, - gating: ExternalFunctionSchema.nullish(), - globalGating: z.string().nullish(), + gating: ExternalFunctionSchema.nullable(), + globalGating: z.string().nullable(), }) .refine( opts => opts.gating != null || opts.globalGating != null, @@ -146,7 +146,7 @@ export type Hook = z.infer; */ const EnvironmentConfigSchema = z.object({ - customHooks: z.map(z.string(), HookSchema).optional().default(new Map()), + customHooks: z.map(z.string(), HookSchema).default(new Map()), /** * A function that, given the name of a module, can optionally return a description @@ -247,7 +247,7 @@ const EnvironmentConfigSchema = z.object({ * * The symbol configuration is set for backwards compatability with pre-React 19 transforms */ - inlineJsxTransform: ReactElementSymbolSchema.nullish(), + inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null), /* * Enable validation of hooks to partially check that the component honors the rules of hooks. @@ -338,9 +338,9 @@ const EnvironmentConfigSchema = z.object({ * } * } */ - enableEmitFreeze: ExternalFunctionSchema.nullish(), + enableEmitFreeze: ExternalFunctionSchema.nullable().default(null), - enableEmitHookGuards: ExternalFunctionSchema.nullish(), + enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null), /** * Enable instruction reordering. See InstructionReordering.ts for the details @@ -424,7 +424,7 @@ const EnvironmentConfigSchema = z.object({ * } * */ - enableEmitInstrumentForget: InstrumentationSchema.nullish(), + enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null), // Enable validation of mutable ranges assertValidMutableRanges: z.boolean().default(false), @@ -463,8 +463,6 @@ const EnvironmentConfigSchema = z.object({ */ throwUnknownException__testonly: z.boolean().default(false), - enableSharedRuntime__testonly: z.boolean().default(false), - /** * Enables deps of a function epxression to be treated as conditional. This * makes sure we don't load a dep when it's a property (to check if it has @@ -502,7 +500,8 @@ const EnvironmentConfigSchema = z.object({ * computed one. This detects cases where rules of react violations may cause the * compiled code to behave differently than the original. */ - enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(), + enableChangeDetectionForDebugging: + ExternalFunctionSchema.nullable().default(null), /** * The react native re-animated library uses custom Babel transforms that @@ -542,7 +541,7 @@ const EnvironmentConfigSchema = z.object({ * * Here the variables `ref` and `myRef` will be typed as Refs. */ - enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false), + enableTreatRefLikeIdentifiersAsRefs: z.boolean().default(false), /* * If specified a value, the compiler lowers any calls to `useContext` to use @@ -564,11 +563,55 @@ const EnvironmentConfigSchema = z.object({ * const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]); * ``` */ - lowerContextAccess: ExternalFunctionSchema.nullish(), + lowerContextAccess: ExternalFunctionSchema.nullable().default(null), }); export type EnvironmentConfig = z.infer; +/** + * For test fixtures and playground only. + * + * Pragmas are straightforward to parse for boolean options (`:true` and + * `:false`). These are 'enabled' config values for non-boolean configs (i.e. + * what is used when parsing `:true`). + */ +const testConfigValues: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: '__DEV__', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, +}; + +/** + * For snap test fixtures and playground only. + */ export function parseConfigPragma(pragma: string): EnvironmentConfig { const maybeConfig: any = {}; // Get the defaults to programmatically check for boolean properties @@ -579,21 +622,12 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } const keyVal = token.slice(1); - let [key, val]: any = keyVal.split(':'); + let [key, val = undefined] = keyVal.split(':'); + const isSet = val === undefined || val === 'true'; - if (key === 'validateNoCapitalizedCalls') { - maybeConfig[key] = []; - continue; - } - - if ( - key === 'enableChangeDetectionForDebugging' && - (val === undefined || val === 'true') - ) { - maybeConfig[key] = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; + if (isSet && key in testConfigValues) { + maybeConfig[key] = + testConfigValues[key as keyof PartialEnvironmentConfig]; continue; } @@ -608,7 +642,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { props.push({type: 'name', name: elt}); } } - console.log([valSplit[0], props.map(x => x.name ?? '*').join('.')]); maybeConfig[key] = [[valSplit[0], props]]; } continue; @@ -619,11 +652,10 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } if (val === undefined || val === 'true') { - val = true; + maybeConfig[key] = true; } else { - val = false; + maybeConfig[key] = false; } - maybeConfig[key] = val; } const config = EnvironmentConfigSchema.safeParse(maybeConfig); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md index dd67bcfbff..9a59b36cc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); @@ -18,7 +18,7 @@ import { shouldInstrument, makeReadOnly, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { if (__DEV__ && shouldInstrument) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js index 4edff1c3fc..bd66353319 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js @@ -1,4 +1,4 @@ -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md index 4aa29992eb..fc9247344d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; @@ -25,7 +25,7 @@ function Foo(props) { ```javascript import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) @gating +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js index 85fbd97ee7..dffb8ce795 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index ba8ed5056b..b5da853b6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 8947503277..2aef527e6b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md index 2078575e83..cf42c0ce75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; @@ -60,7 +60,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c2 } from "react/compiler-runtime"; // @enableInlineJsxTransform +import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform function Parent(t0) { const $ = _c2(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js index 6fe9553dcd..e7487e2b36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js @@ -1,4 +1,4 @@ -// @enableInlineJsxTransform +// @inlineJsxTransform function Parent({children, a: _a, b: _b, c: _c, ref}) { return
{children}
; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index cd907575fb..a43ca48a6a 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -21,7 +21,6 @@ import type { } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { - EnvironmentConfig, Macro, MacroMethod, parseConfigPragma as ParseConfigPragma, @@ -37,6 +36,11 @@ export function parseLanguage(source: string): 'flow' | 'typescript' { return source.indexOf('@flow') !== -1 ? 'flow' : 'typescript'; } +/** + * Parse react compiler plugin + environment options from test fixture. Note + * that although this primarily uses `Environment:parseConfigPragma`, it also + * has test fixture specific (i.e. not applicable to playground) parsing logic. + */ function makePluginOptions( firstLine: string, parseConfigPragmaFn: typeof ParseConfigPragma, @@ -44,15 +48,11 @@ function makePluginOptions( ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { let gating = null; - let enableEmitInstrumentForget = null; - let enableEmitFreeze = null; - let enableEmitHookGuards = null; let compilationMode: CompilationMode = 'all'; let panicThreshold: PanicThresholdOptions = 'all_errors'; let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let enableChangeDetectionForDebugging = null; let customMacros: null | Array = null; let validateBlocklistedImports = null; let target = '19' as const; @@ -78,31 +78,6 @@ function makePluginOptions( importSpecifierName: 'isForgetEnabled_Fixtures', }; } - if (firstLine.includes('@instrumentForget')) { - enableEmitInstrumentForget = { - fn: { - source: 'react-compiler-runtime', - importSpecifierName: 'useRenderCounter', - }, - gating: { - source: 'react-compiler-runtime', - importSpecifierName: 'shouldInstrument', - }, - globalGating: '__DEV__', - }; - } - if (firstLine.includes('@enableEmitFreeze')) { - enableEmitFreeze = { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }; - } - if (firstLine.includes('@enableEmitHookGuards')) { - enableEmitHookGuards = { - source: 'react-compiler-runtime', - importSpecifierName: '$dispatcherGuard', - }; - } const targetMatch = /@target="([^"]+)"/.exec(firstLine); if (targetMatch) { @@ -132,16 +107,18 @@ function makePluginOptions( ignoreUseNoForget = true; } + /** + * Snap currently runs all fixtures without `validatePreserveExistingMemo` as + * most fixtures are interested in compilation output, not whether the + * compiler was able to preserve existing memo. + * + * TODO: flip the default. `useMemo` is rare in test fixtures -- fixtures that + * use useMemo should be explicit about whether this flag is enabled + */ if (firstLine.includes('@validatePreserveExistingMemoizationGuarantees')) { validatePreserveExistingMemoizationGuarantees = true; } - if (firstLine.includes('@enableChangeDetectionForDebugging')) { - enableChangeDetectionForDebugging = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; - } const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); if ( hookPatternMatch && @@ -196,20 +173,6 @@ function makePluginOptions( .map(s => s.trim()) .filter(s => s.length > 0); } - - let lowerContextAccess = null; - if (firstLine.includes('@lowerContextAccess')) { - lowerContextAccess = { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }; - } - - let inlineJsxTransform: EnvironmentConfig['inlineJsxTransform'] = null; - if (firstLine.includes('@enableInlineJsxTransform')) { - inlineJsxTransform = {elementSymbol: 'react.transitional.element'}; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -229,17 +192,10 @@ function makePluginOptions( ValueKindEnum, }), customMacros, - enableEmitFreeze, - enableEmitInstrumentForget, - enableEmitHookGuards, assertValidMutableRanges: true, - enableSharedRuntime__testonly: true, hookPattern, validatePreserveExistingMemoizationGuarantees, - enableChangeDetectionForDebugging, - lowerContextAccess, validateBlocklistedImports, - inlineJsxTransform, }, compilationMode, logger, From 515134d33c066404bf1fb97223021016a2eefb6d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 10/63] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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 = ( + + ); + $[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') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"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-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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}, + ], +}; From df7648c6e5779262b1dc43f7768a48c1d2ff9994 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:03 -0700 Subject: [PATCH 11/63] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx 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 d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - 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 @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + 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 collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -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, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( 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), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + 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), 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 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":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/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From 3c4549a102dcc5390de173c892927af13b9e3f96 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:04 -0700 Subject: [PATCH 12/63] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 2 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1861 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4f37e6e89c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,8 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - /** * Enables inference of optional dependency chains. Without this flag * a property chain such as `props?.items?.foo` will infer as a dep on diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; 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 index 4d45d3f3c6..352552bf02 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 85952e3cec16a84935940e0d2700b9b3fc175389 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 10:55:47 -0700 Subject: [PATCH 13/63] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for the next one. Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) 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 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, 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 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From c7169c0a75a19d0e49edabf4dd4213567fb57d18 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 10:55:47 -0700 Subject: [PATCH 14/63] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects: - optional-chaining deps for inner functions - full DCE and outlining for inner functions - fewer extraneous instructions --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4f37e6e89c..8394eaaacf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inference of optional dependency chains. Without this flag * a property chain such as `props?.items?.foo` will infer as a dep on 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 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { 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 index 02e60eff91..ed56ff0681 100644 --- 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 @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } 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 index 157e2de81a..bb99a5d90f 100644 --- 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 @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From ddf52ed1e1b5e40105a2a4f9ed004d7b3bba8520 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 10:55:47 -0700 Subject: [PATCH 15/63] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From d9b8b79517f018ce548055c87e0301fe4485b0f3 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 10:55:47 -0700 Subject: [PATCH 16/63] [compiler][be] Patch test fixtures for evaluator --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From 93ebd240c5c4077971d0828bd154b6165a22c26d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 10:55:47 -0700 Subject: [PATCH 17/63] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 28a2bdccb282f327f764de9ca58a1462092fbbfd Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 10:55:47 -0700 Subject: [PATCH 18/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a HIR-rewrite version that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 10 -- .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 341 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 8394eaaacf..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enableFunctionDependencyRewrite: z.boolean().default(true), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 9c02c8758c952ccad940ae05b8b314ae64bacecd Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 19/63] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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 = ( + + ); + $[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') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"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-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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}, + ], +}; From cbd12f5fcd98e4e92c3d06f9e5f3c89871ab6399 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 20/63] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). - --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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 = ( + + ); + $[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') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"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-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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}, + ], +}; From fad780511ab81cfed76b069b9a4d54ed57068b46 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:03 -0700 Subject: [PATCH 21/63] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions - --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx 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 d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - 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 @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + 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 collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -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, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( 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), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + 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), 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 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":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/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From 1094c77f88e383a293a127044fdbac05b8727ba7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:04 -0700 Subject: [PATCH 22/63] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR - --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; 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 index 4d45d3f3c6..352552bf02 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 281d16137531363f368c43f8098cdb71cb2bd658 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:10:41 -0700 Subject: [PATCH 23/63] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for the next one. Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR - --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) 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 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, 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 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From a84a59d895312184a2b32b7903848f5825532985 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 24/63] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4c57e792e3..31e42049fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime 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 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { 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 index 02e60eff91..ed56ff0681 100644 --- 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 @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } 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 index 157e2de81a..bb99a5d90f 100644 --- 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 @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From a210420d89c9bdedca288917bc53b9e302a38321 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 25/63] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) - --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From 6c996df05bc2426bb1fa56a93212744fbe1e09ec Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 26/63] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s - --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From fd649b50acacffa2eaa1985712accab9277c3776 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 27/63] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) - --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 3c0b16a3d2e1adac5b814d39014215d71101f9cc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:15 -0700 Subject: [PATCH 28/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. - --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 332 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 1c6a90e74cfb5f2c1fb48e8b2c775b3b47088942 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 29/63] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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 = ( + + ); + $[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') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"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-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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}, + ], +}; From ab8d3c5bdee07fcc7ce9f26c90bbd3916cfebe5c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:03 -0700 Subject: [PATCH 30/63] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx 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 d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - 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 @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + 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 collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -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, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( 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), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + 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), 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 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":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/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From fba4c34063ede95354448bf59093c557fc441d8d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 23 Oct 2024 11:10:04 -0700 Subject: [PATCH 31/63] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; 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 index 4d45d3f3c6..352552bf02 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 01cf67c06c5c2585549615ee55a16bcd429295b4 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:10:41 -0700 Subject: [PATCH 32/63] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for the next one. Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) 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 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, 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 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From c5b7421f24600cc8fc51dd654bd89ee6b068a074 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 33/63] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4c57e792e3..31e42049fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime 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 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { 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 index 02e60eff91..ed56ff0681 100644 --- 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 @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } 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 index 157e2de81a..bb99a5d90f 100644 --- 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 @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 1ff42cd89d5fc56644370aa7c549a05c310af309 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 34/63] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From a1f5b655e4b8fa9ebb43ee307d164f6424e7e1cc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 35/63] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From ad0560f503e09293265c1f08c07064133c0841a3 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:01 -0700 Subject: [PATCH 36/63] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) ' --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 8e5c4c1bd61a8e0fdfae3e38c9d30f3f6e11dcce Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:11:15 -0700 Subject: [PATCH 37/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 332 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From a5b8191460d85318d36f111c76792939cd241a61 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:14:18 -0700 Subject: [PATCH 38/63] [compiler] Add fixture for objectexpr computed key bug We're currently bailing out on complex computed-key syntax as we assumed that caused bugs (due to inferring computed key rvalues to have `freeze` effects). This fixture shows that this bailout is unrelated to the underlying bug --- ...nstruction-hoisted-sequence-expr.expect.md | 109 ++++++++++++++++++ ...fter-construction-hoisted-sequence-expr.js | 31 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 141 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md new file mode 100644 index 0000000000..dcca6cddd8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md @@ -0,0 +1,109 @@ + +## Input + +```javascript +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, mutate } from "shared-runtime"; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const $ = _c(8); + let t0; + let key; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + key = {}; + t0 = (mutate(key), key); + $[0] = t0; + $[1] = key; + } else { + t0 = $[0]; + key = $[1]; + } + const tmp = t0; + let t1; + if ($[2] !== props.value) { + t1 = identity([props.value]); + $[2] = props.value; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== t1) { + t2 = { [tmp]: t1 }; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + const context = t2; + + mutate(key); + let t3; + if ($[6] !== context) { + t3 = [context, key]; + $[6] = context; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], + sequentialRenders: [{ value: 42 }, { value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js new file mode 100644 index 0000000000..94befbdd17 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js @@ -0,0 +1,31 @@ +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..76914f1dd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -478,6 +478,7 @@ const skipFilter = new Set([ // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', + 'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr', 'bug-invalid-hoisting-functionexpr', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', From 673a25ba8ce7957eed8727e5f7a0dd7034034108 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 17:17:31 -0700 Subject: [PATCH 39/63] [compiler][ez] Fixture repro for function hoisting bug Repro for bug reported by @alexmckenley --- .../bug-functiondecl-hoisting.expect.md | 81 +++++++++++++++++++ .../compiler/bug-functiondecl-hoisting.tsx | 22 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 104 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000..bd567de90d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -0,0 +1,81 @@ + +## Input + +```javascript +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo(t0) { + const $ = _c(6); + const { value } = t0; + let bar; + let result; + if ($[0] !== value) { + result = bar(); + bar = function bar() { + return { value }; + }; + $[0] = value; + $[1] = bar; + $[2] = result; + } else { + bar = $[1]; + result = $[2]; + } + + const t1 = bar; + let t2; + if ($[3] !== result || $[4] !== t1) { + t2 = ; + $[3] = result; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx new file mode 100644 index 0000000000..3500a0629d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx @@ -0,0 +1,22 @@ +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..0c7ab28ac0 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-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-invalid-hoisting-functionexpr', + 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', From 2b8c1218b7b905c0eb243ab767dc7e4ceda345ac Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 17:06:11 -0700 Subject: [PATCH 40/63] [compiler][ez] tsconfig: treat all snap fixtures as modules Qol improvement. Currently, typescript lints treat test fixtures without an export as a 'global script' (see [docs](https://www.typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-defined)). This gives confusing lints for duplicate declarations (in the global scope) --- .../src/__tests__/fixtures/tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json index 07d6d2baae..7c51d213ed 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json @@ -19,7 +19,9 @@ }, "verbatimModuleSyntax": true, "module": "ESNext", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "moduleDetection": "force" + }, "include": [ "./compiler/**/*.js", From 18b760e99d3ac8d0efb40f4226a7b773dba4c2bb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 11:25:24 -0700 Subject: [PATCH 41/63] [compiler][ez] Fixture repro for function hoisting bug Repro for bug reported by @alexmckenley --- .../bug-functiondecl-hoisting.expect.md | 81 +++++++++++++++++++ .../compiler/bug-functiondecl-hoisting.tsx | 22 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + 3 files changed, 104 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000..2b0031b117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -0,0 +1,81 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo(t0) { + const $ = _c(6); + const { value } = t0; + let bar; + let result; + if ($[0] !== value) { + result = bar(); + bar = function bar() { + return { value }; + }; + $[0] = value; + $[1] = bar; + $[2] = result; + } else { + bar = $[1]; + result = $[2]; + } + + const t1 = bar; + let t2; + if ($[3] !== result || $[4] !== t1) { + t2 = ; + $[3] = result; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx new file mode 100644 index 0000000000..c454101282 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx @@ -0,0 +1,22 @@ +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..0c7ab28ac0 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-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', 'bug-invalid-hoisting-functionexpr', + 'bug-functiondecl-hoisting', 'bug-try-catch-maybe-null-dependency', 'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted', 'bug-invalid-phi-as-dependency', From d0c31e9dd2c43b152b67715beba5181e4d620bf0 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 42/63] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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 = ( + + ); + $[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') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"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-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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}, + ], +}; From ce0115c95a30f3cf8243491aecde00db2e442ee9 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 43/63] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx 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 d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - 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 @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + 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 collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -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, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( 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), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + 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), 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 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":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/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From 640c52ef9a075cadc40776e20ca3e9efd584e1e6 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 44/63] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index b85d9425cb..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,16 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; 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 index 4d45d3f3c6..352552bf02 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 0d4afc594107049e3380c3fabb32d1503ea47d74 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 45/63] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) 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 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, 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 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From 754d9338c1540b5e6ed08e67684f5d147a1a8da5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 46/63] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 4c57e792e3..31e42049fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,6 +230,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime 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 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { 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 index 02e60eff91..ed56ff0681 100644 --- 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 @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } 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 index 157e2de81a..bb99a5d90f 100644 --- 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 @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From 857947fda6371e0815d7a60144c95e524f54ac43 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 47/63] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a179224a77..a772be62aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From ded2bf25402ba0da5c9abbe0dba53c375288ce33 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 48/63] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From 24f726629a103fb64e13bfb39bcef40333a4d490 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 49/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 36 files changed, 111 insertions(+), 332 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 60152cad6654b1a2e34791a22abca0d57586eb07 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 24 Oct 2024 12:23:22 -0700 Subject: [PATCH 50/63] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) ' --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From a392efd887d82c92e9808bb36e246da2af7e705e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 11:36:53 -0700 Subject: [PATCH 51/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 20 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 131 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..4e8dc74419 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -7,12 +7,15 @@ import {CompilerError} from '../CompilerError'; import {BlockId, HIRFunction, Identifier, Place} from '../HIR/HIR'; +import {printHIR, printIdentifier} from '../HIR/PrintHIR'; import { eachInstructionLValue, eachInstructionOperand, eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +144,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 6b5eba2d14c47fe0a986f1e19f1a534222d4b9eb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 11:36:53 -0700 Subject: [PATCH 52/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 130 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..3248775f7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,7 +231,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 31f569635681521ec6bce8c9cad3ccdadd2c123c Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 20:58:00 -0600 Subject: [PATCH 53/63] [compiler][be] Stabilize compiler output: sort deps and decls by name All dependencies and declarations of a reactive scope can be reordered to scope start/end. i.e. generated code does not depend on conditional short-circuiting logic as dependencies are inferred to have no side effects. Sorting these by name helps us get higher signal compilation snapshot diffs when upgrading the compiler and testing PRs internally at Meta --- .../ReactiveScopes/CodegenReactiveFunction.ts | 36 +++++++++++++++++-- .../compiler/array-at-effect.expect.md | 6 ++-- .../compiler/array-property-call.expect.md | 10 +++--- .../bug-invalid-phi-as-dependency.expect.md | 6 ++-- ...fun-alias-captured-mutate-2-iife.expect.md | 6 ++-- ...ring-fun-alias-captured-mutate-2.expect.md | 6 ++-- ...alias-captured-mutate-arr-2-iife.expect.md | 6 ++-- ...-fun-alias-captured-mutate-arr-2.expect.md | 6 ++-- ...c-alias-captured-mutate-arr-iife.expect.md | 6 ++-- ...g-func-alias-captured-mutate-arr.expect.md | 6 ++-- ...-func-alias-captured-mutate-iife.expect.md | 6 ++-- ...uring-func-alias-captured-mutate.expect.md | 6 ++-- ...turing-function-member-expr-call.expect.md | 6 ++-- .../fixtures/compiler/component.expect.md | 12 +++---- .../compiler/dependencies-outputs.expect.md | 6 ++-- .../destructure-in-branch-ssa.expect.md | 8 ++--- ...g-same-property-identifier-names.expect.md | 6 ++-- .../fixtures/compiler/destructuring.expect.md | 34 +++++++++--------- ...er-declaration-of-previous-scope.expect.md | 10 +++--- .../existing-variables-with-c-name.expect.md | 6 ++-- .../compiler/fast-refresh-reloading.expect.md | 6 ++-- ...-mutable-range-destructured-prop.expect.md | 6 ++-- ...btparam-with-jsx-element-content.expect.md | 6 ++-- ...onmutating-loop-local-collection.expect.md | 6 ++-- ...pression-prototype-call-mutating.expect.md | 6 ++-- ...unctionexpr-conditional-access-2.expect.md | 6 ++-- .../functionexpr–conditional-access.expect.md | 6 ++-- ...bals-dont-resolve-local-useState.expect.md | 6 ++-- .../fixtures/compiler/hook-noAlias.expect.md | 6 ++-- .../compiler/hooks-with-prefix.expect.md | 6 ++-- ...incompatible-destructuring-kinds.expect.md | 14 ++++---- ...-promoted-to-outer-scope-dynamic.expect.md | 20 +++++------ ...jsx-outlining-child-stored-in-id.expect.md | 12 +++---- .../jsx-outlining-jsx-stored-in-id.expect.md | 18 +++++----- .../jsx-outlining-separate-nested.expect.md | 22 ++++++------ .../compiler/jsx-outlining-simple.expect.md | 18 +++++----- ...-tag-evaluation-order-non-global.expect.md | 10 +++--- .../lambda-capture-returned-alias.expect.md | 6 ++-- .../lower-context-acess-multiple.expect.md | 6 ++-- .../lower-context-selector-simple.expect.md | 6 ++-- ...ge-consecutive-scopes-reordering.expect.md | 6 ++-- .../compiler/method-call-computed.expect.md | 8 ++--- .../compiler/method-call-fn-call.expect.md | 8 ++--- .../fixtures/compiler/method-call.expect.md | 6 ++-- ...pture-in-unsplittable-memo-block.expect.md | 10 +++--- .../object-shorthand-method-nested.expect.md | 6 ++-- ...al-member-expression-as-memo-dep.expect.md | 6 ++-- ...ession-single-with-unconditional.expect.md | 6 ++-- ...ptional-member-expression-single.expect.md | 6 ++-- ...ession-with-conditional-optional.expect.md | 12 +++---- ...mber-expression-with-conditional.expect.md | 12 +++---- ...rly-return-within-reactive-scope.expect.md | 10 +++--- ...Callback-in-other-reactive-block.expect.md | 8 ++--- ...k-reordering-deplist-controlflow.expect.md | 16 ++++----- ...useMemo-conditional-access-alloc.expect.md | 6 ++-- ...eMemo-conditional-access-noAlloc.expect.md | 6 ++-- .../useMemo-in-other-reactive-block.expect.md | 8 ++--- ...-reordering-depslist-controlflow.expect.md | 6 ++-- ...signed-loop-force-scopes-enabled.expect.md | 8 ++--- ...al-member-expression-as-memo-dep.expect.md | 6 ++-- ...ession-single-with-unconditional.expect.md | 6 ++-- ...ptional-member-expression-single.expect.md | 6 ++-- ...rly-return-within-reactive-scope.expect.md | 10 +++--- ...r-function-cond-access-local-var.expect.md | 6 ++-- ...function-cond-access-not-hoisted.expect.md | 6 ++-- ...n-uncond-access-hoists-other-dep.expect.md | 6 ++-- ...uncond-optional-hoists-other-dep.expect.md | 12 +++---- .../promote-uncond.expect.md | 8 ++--- .../switch-non-final-default.expect.md | 16 ++++----- .../switch.expect.md | 16 ++++----- ...-analysis-interleaved-reactivity.expect.md | 6 ++-- ...ve-via-mutation-of-computed-load.expect.md | 6 ++-- ...ve-via-mutation-of-property-load.expect.md | 6 ++-- .../reassignment-separate-scopes.expect.md | 16 ++++----- ...eactive-cond-deps-break-in-scope.expect.md | 6 ++-- ...active-cond-deps-return-in-scope.expect.md | 16 ++++----- ...function-cond-access-not-hoisted.expect.md | 6 ++-- .../hoist-deps-diff-ssa-instance.expect.md | 6 ++-- .../jump-poisoned/break-in-scope.expect.md | 6 ++-- .../loop-break-in-scope.expect.md | 6 ++-- ...e-if-nonexhaustive-poisoned-deps.expect.md | 10 +++--- ...-if-nonexhaustive-poisoned-deps1.expect.md | 10 +++--- .../jump-poisoned/return-in-scope.expect.md | 16 ++++----- .../return-poisons-outer-scope.expect.md | 10 +++--- ...p-target-within-scope-loop-break.expect.md | 6 ++-- ...e-if-exhaustive-nonpoisoned-deps.expect.md | 10 +++--- ...-if-exhaustive-nonpoisoned-deps1.expect.md | 10 +++--- .../promote-uncond.expect.md | 6 ++-- ...duce-if-exhaustive-poisoned-deps.expect.md | 18 +++++----- .../subpath-order1.expect.md | 6 ++-- .../superpath-order1.expect.md | 6 ++-- .../uncond-access-in-mutable-range.expect.md | 6 ++-- .../reordering-across-blocks.expect.md | 6 ++-- ...ed-property-load-for-method-call.expect.md | 6 ++-- ...uned-scope-leaks-value-via-alias.expect.md | 6 ++-- ...invalid-pruned-scope-leaks-value.expect.md | 6 ++-- ...o-invalid-reactivity-value-block.expect.md | 6 ++-- ...lack-of-phi-types-explicit-types.expect.md | 6 ++-- ...ng-memoization-lack-of-phi-types.expect.md | 6 ++-- .../repro-no-value-for-temporary.expect.md | 6 ++-- ...ro-propagate-type-of-ternary-jsx.expect.md | 8 ++--- ...epro-slow-validate-preserve-memo.expect.md | 6 ++-- ...ble-code-early-return-in-useMemo.expect.md | 6 ++-- .../fixtures/compiler/repro.expect.md | 10 +++--- .../rest-param-with-array-pattern.expect.md | 6 ++-- .../rest-param-with-identifier.expect.md | 6 ++-- ...param-with-object-spread-pattern.expect.md | 6 ++-- ...s-dep-and-redeclare-maybe-frozen.expect.md | 14 ++++---- ...me-variable-as-dep-and-redeclare.expect.md | 24 ++++++------- ...assignment-to-scope-declarations.expect.md | 14 ++++---- ...ixed-local-and-scope-declaration.expect.md | 14 ++++---- .../switch-non-final-default.expect.md | 16 ++++----- .../fixtures/compiler/switch.expect.md | 16 ++++----- .../todo.jsx-outlining-children.expect.md | 12 +++---- ...odo.jsx-outlining-duplicate-prop.expect.md | 12 +++---- ...ntext-access-array-destructuring.expect.md | 6 ++-- ...text-access-destructure-multiple.expect.md | 6 ++-- ...r-context-access-mixed-array-obj.expect.md | 6 ++-- ...text-access-nested-destructuring.expect.md | 6 ++-- ...wer-context-access-property-load.expect.md | 6 ++-- .../try-catch-in-nested-scope.expect.md | 16 ++++----- .../try-catch-with-catch-param.expect.md | 10 +++--- .../compiler/try-catch-with-return.expect.md | 10 +++--- ...type-provider-log-default-import.expect.md | 12 +++---- .../compiler/type-provider-log.expect.md | 12 +++---- ...r-store-capture-namespace-import.expect.md | 20 +++++------ .../type-provider-store-capture.expect.md | 20 +++++------ .../fixtures/compiler/unary-expr.expect.md | 24 ++++++------- .../use-operator-call-expression.expect.md | 6 ++-- .../use-operator-conditional.expect.md | 6 ++-- .../use-operator-method-call.expect.md | 6 ++-- .../useEffect-nested-lambdas.expect.md | 6 ++-- .../useState-unpruned-dependency.expect.md | 6 ++-- 133 files changed, 633 insertions(+), 601 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 297c771254..1841c5322d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -572,7 +572,22 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + const sortedDeps = [...scope.dependencies].sort((a, b) => { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && + b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected dependency to be named', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; + }); + for (const dep of sortedDeps) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +630,24 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + // TODO Map + + const sortedDecls = [...scope.declarations].sort(([, a], [, b]) => { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && + b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected dependency to be named', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; + }); + for (const [, {identifier}] of sortedDecls) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6..a8bad51215 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803..6618be4a6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md index 32e2c9fd64..09d2d8800b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -70,10 +70,10 @@ function Component() { throw new Error("invariant broken"); } let t1; - if ($[1] !== obj || $[2] !== boxedInner) { + if ($[1] !== boxedInner || $[2] !== obj) { t1 = ; - $[1] = obj; - $[2] = boxedInner; + $[1] = boxedInner; + $[2] = obj; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c..5e0b32709b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68bade..f9ce3f2e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d..81737a1ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a..38590d1559 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbc..4882aa822f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740..7c94c33e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d7048756..60493dd25a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..14532562fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fe..cab9c9a500 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c..c6037fd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb19..f0f9911c07 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc8..b159789106 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922..3e1c4771ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558c..f292e83e16 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c..ce5bfda644 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 0d671a3de2..5cde3bde23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -61,10 +61,10 @@ function Component(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== array) { + if ($[4] !== array || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = array; + $[4] = array; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md index ecd03a0b10..4175d23fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md @@ -59,10 +59,10 @@ function Component(props) { t3 = $[4]; } let t4; - if ($[5] !== t3 || $[6] !== doubled) { + if ($[5] !== doubled || $[6] !== t3) { t4 = ; - $[5] = t3; - $[6] = doubled; + $[5] = doubled; + $[6] = t3; $[7] = t4; } else { t4 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md index d56f7a98ad..9bb651aa67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbt-repro-invalid-mutable-range-destructured-prop.expect.md @@ -61,10 +61,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== el) { + if ($[4] !== el || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = el; + $[4] = el; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md index d58c25b510..56ffb70cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/fbtparam-with-jsx-element-content.expect.md @@ -32,7 +32,7 @@ function Component(t0) { const $ = _c(4); const { name, data, icon } = t0; let t1; - if ($[0] !== name || $[1] !== icon || $[2] !== data) { + if ($[0] !== data || $[1] !== icon || $[2] !== name) { t1 = ( {fbt._( @@ -61,9 +61,9 @@ function Component(t0) { )} ); - $[0] = name; + $[0] = data; $[1] = icon; - $[2] = data; + $[2] = name; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md index cff8c5bd13..4abe630044 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-of-nonmutating-loop-local-collection.expect.md @@ -91,10 +91,10 @@ function Component(t0) { t5 = $[9]; } let t6; - if ($[10] !== x || $[11] !== b) { + if ($[10] !== b || $[11] !== x) { t6 = [x, b]; - $[10] = x; - $[11] = b; + $[10] = b; + $[11] = x; $[12] = t6; } else { t6 = $[12]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md index be59673c15..9888e96222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expression-prototype-call-mutating.expect.md @@ -59,10 +59,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== a_0) { + if ($[4] !== a_0 || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = a_0; + $[4] = a_0; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..32498e1379 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -36,10 +36,10 @@ function Component(t0) { } const f = t1; let t2; - if ($[2] !== props || $[3] !== f) { + if ($[2] !== f || $[3] !== props) { t2 = props == null ? _temp : f; - $[2] = props; - $[3] = f; + $[2] = f; + $[3] = props; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..4a62bf6f24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -36,10 +36,10 @@ function Component(props) { } const getLength = t0; let t1; - if ($[2] !== props.bar || $[3] !== getLength) { + if ($[2] !== getLength || $[3] !== props.bar) { t1 = props.bar && getLength(); - $[2] = props.bar; - $[3] = getLength; + $[2] = getLength; + $[3] = props.bar; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md index 7548a3b639..be7f3f1bd2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/globals-dont-resolve-local-useState.expect.md @@ -56,10 +56,10 @@ function Component() { t0 = $[1]; } let t1; - if ($[2] !== t0 || $[3] !== state) { + if ($[2] !== state || $[3] !== t0) { t1 =
{state}
; - $[2] = t0; - $[3] = state; + $[2] = state; + $[3] = t0; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md index 96ccd1e2f1..329d57a035 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-noAlias.expect.md @@ -41,10 +41,10 @@ function Component(props) { console.log(props); }, [props.a]); let t1; - if ($[2] !== x || $[3] !== item) { + if ($[2] !== item || $[3] !== x) { t1 = [x, item]; - $[2] = x; - $[3] = item; + $[2] = item; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index f7e02a53f1..085df625f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -73,15 +73,15 @@ function Component() { t1 = $[4]; } let t3; - if ($[5] !== t2 || $[6] !== json) { + if ($[5] !== json || $[6] !== t2) { t3 = (
{t2} {json}
); - $[5] = t2; - $[6] = json; + $[5] = json; + $[6] = t2; $[7] = t3; } else { t3 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md index 970cc50f03..8afc59a80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/incompatible-destructuring-kinds.expect.md @@ -29,22 +29,22 @@ import { Stringify } from "shared-runtime"; function Component(t0) { const $ = _c(4); - let t1; let a; let b; + let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { a = "a"; const [t2, t3] = [null, null]; t1 = t3; a = t2; - $[0] = t1; - $[1] = a; - $[2] = b; + $[0] = a; + $[1] = b; + $[2] = t1; } else { - t1 = $[0]; - a = $[1]; - b = $[2]; + a = $[0]; + b = $[1]; + t1 = $[2]; } b = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md index becc4066e7..735462657f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inner-memo-value-not-promoted-to-outer-scope-dynamic.expect.md @@ -27,10 +27,10 @@ function Component(props) { const $ = _c(15); const item = useFragment(FRAGMENT, props.item); useFreeze(item); - let t0; let T0; - let t1; let T1; + let t0; + let t1; if ($[0] !== item) { const count = new MaybeMutable(item); @@ -44,15 +44,15 @@ function Component(props) { } t0 = maybeMutate(count); $[0] = item; - $[1] = t0; - $[2] = T0; - $[3] = t1; - $[4] = T1; + $[1] = T0; + $[2] = T1; + $[3] = t0; + $[4] = t1; } else { - t0 = $[1]; - T0 = $[2]; - t1 = $[3]; - T1 = $[4]; + T0 = $[1]; + T1 = $[2]; + t0 = $[3]; + t1 = $[4]; } let t2; if ($[6] !== t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..b84229156b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -83,10 +83,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -98,15 +98,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md index 496282e1ef..7fca963134 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md @@ -52,7 +52,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -66,8 +66,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -94,10 +94,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -109,15 +109,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md index 7f86546cd4..9d2b254c06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md @@ -60,7 +60,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -73,8 +73,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -117,7 +117,7 @@ function _temp(t0) { t3 = $[5]; } let t4; - if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) { + if ($[6] !== t1 || $[7] !== t2 || $[8] !== t3 || $[9] !== x) { t4 = ( {t1} @@ -125,10 +125,10 @@ function _temp(t0) { {t3} ); - $[6] = x; - $[7] = t1; - $[8] = t2; - $[9] = t3; + $[6] = t1; + $[7] = t2; + $[8] = t3; + $[9] = x; $[10] = t4; } else { t4 = $[10]; @@ -140,15 +140,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md index 9e268227a2..09323f5ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => { @@ -63,8 +63,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,10 +91,10 @@ function _temp(t0) { t1 = $[1]; } let t2; - if ($[2] !== x || $[3] !== t1) { + if ($[2] !== t1 || $[3] !== x) { t2 = {t1}; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; $[4] = t2; } else { t2 = $[4]; @@ -106,15 +106,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md index b1dfbc61c3..aeb0cdf88c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md @@ -53,8 +53,8 @@ function maybeMutate(x) {} function Component(props) { const $ = _c(11); - let Tag; let T0; + let Tag; let t0; if ($[0] !== props.component || $[1] !== props.alternateComponent) { const maybeMutable = new MaybeMutable(); @@ -64,12 +64,12 @@ function Component(props) { t0 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable)); $[0] = props.component; $[1] = props.alternateComponent; - $[2] = Tag; - $[3] = T0; + $[2] = T0; + $[3] = Tag; $[4] = t0; } else { - Tag = $[2]; - T0 = $[3]; + T0 = $[2]; + Tag = $[3]; t0 = $[4]; } let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md index e172ee1039..bac21217c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-capture-returned-alias.expect.md @@ -44,7 +44,7 @@ function CaptureNotMutate(props) { } const idx = t0; let aliasedElement; - if ($[2] !== props.el || $[3] !== idx) { + if ($[2] !== idx || $[3] !== props.el) { const element = bar(props.el); const fn = function () { @@ -54,8 +54,8 @@ function CaptureNotMutate(props) { aliasedElement = fn(); mutate(aliasedElement); - $[2] = props.el; - $[3] = idx; + $[2] = idx; + $[3] = props.el; $[4] = aliasedElement; } else { aliasedElement = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md index 47c7a2d743..af9b1df36a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-acess-multiple.expect.md @@ -21,10 +21,10 @@ function App() { const { foo } = useContext_withSelector(MyContext, _temp); const { bar } = useContext_withSelector(MyContext, _temp2); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md index 0b12c2e250..d13682467b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-selector-simple.expect.md @@ -19,10 +19,10 @@ function App() { const $ = _c(3); const { foo, bar } = useContext_withSelector(MyContext, _temp); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md index 5bf1f2cf4d..e5a9081137 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md @@ -60,7 +60,7 @@ function Component() { t2 = $[3]; } let t3; - if ($[4] !== t1 || $[5] !== t0) { + if ($[4] !== t0 || $[5] !== t1) { t3 = (
{t2} @@ -68,8 +68,8 @@ function Component() { {t0}
); - $[4] = t1; - $[5] = t0; + $[4] = t0; + $[5] = t1; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md index 887479c01e..2fc302c8b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-computed.expect.md @@ -43,11 +43,11 @@ function foo(a, b, c) { } const y = t1; let t2; - if ($[4] !== x || $[5] !== y.method || $[6] !== b) { + if ($[4] !== b || $[5] !== x || $[6] !== y.method) { t2 = x[y.method](b); - $[4] = x; - $[5] = y.method; - $[6] = b; + $[4] = b; + $[5] = x; + $[6] = y.method; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md index c32777534b..58c06101d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call-fn-call.expect.md @@ -33,11 +33,11 @@ function foo(a, b, c) { const method = x.method; let t1; - if ($[2] !== method || $[3] !== x || $[4] !== b) { + if ($[2] !== b || $[3] !== method || $[4] !== x) { t1 = method.call(x, b); - $[2] = method; - $[3] = x; - $[4] = b; + $[2] = b; + $[3] = method; + $[4] = x; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md index ad45287d1f..fd8a4935a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/method-call.expect.md @@ -40,10 +40,10 @@ function foo(a, b, c) { } const x = t0; let t1; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { t1 = x.foo(b); - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md index 090e9d889c..a3403e6c8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonmutating-capture-in-unsplittable-memo-block.expect.md @@ -73,8 +73,8 @@ import { identity, mutate } from "shared-runtime"; function useFoo(t0) { const $ = _c(4); const { a, b } = t0; - let z; let y; + let z; if ($[0] !== a || $[1] !== b) { const x = { a }; y = {}; @@ -83,11 +83,11 @@ function useFoo(t0) { mutate(y); $[0] = a; $[1] = b; - $[2] = z; - $[3] = y; + $[2] = y; + $[3] = z; } else { - z = $[2]; - y = $[3]; + y = $[2]; + z = $[3]; } if (z[0] !== y) { throw new Error("oh no!"); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md index 4b500f52d6..dc1cd699d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-shorthand-method-nested.expect.md @@ -40,7 +40,7 @@ function useHook(t0) { const { value } = t0; const [state] = useState(false); let t1; - if ($[0] !== value || $[1] !== state) { + if ($[0] !== state || $[1] !== value) { t1 = { getX() { return { @@ -52,8 +52,8 @@ function useHook(t0) { }; }, }; - $[0] = value; - $[1] = state; + $[0] = state; + $[1] = value; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md index 77875f789d..3dd8e73032 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md index 46767056bd..3cd9877813 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md index 6e44a97b45..60a6171ab1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md index 77ded20d93..2674d78c99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md index 10c23085d8..1d4a50d285 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md @@ -48,19 +48,19 @@ function Component(props) { const t1 = props?.items; let t2; - if ($[3] !== t1 || $[4] !== props.cond) { + if ($[3] !== props.cond || $[4] !== t1) { t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; + $[3] = props.cond; + $[4] = t1; $[5] = t2; } else { t2 = $[5]; } let t3; - if ($[6] !== t2 || $[7] !== data) { + if ($[6] !== data || $[7] !== t2) { t3 = ; - $[6] = t2; - $[7] = data; + $[6] = data; + $[7] = t2; $[8] = t3; } else { t3 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..42b3b21a20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); - let y; let t0; + let y; if ($[0] !== props) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -57,11 +57,11 @@ function Component(props) { } } $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md index 9ca63e23c4..3da133e929 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(7); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; if ($[4] !== minWidth || $[5] !== width) { @@ -55,9 +55,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 947e3bd2eb..ee4e4634cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -42,9 +42,9 @@ import { Stringify } from "shared-runtime"; function Foo(t0) { const $ = _c(8); const { arr1, arr2, foo } = t0; - let t1; let getVal1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + let t1; + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -55,13 +55,13 @@ function Foo(t0) { t1 = () => [y]; foo ? (y = x.concat(arr2)) : y; $[0] = arr1; - $[1] = foo; - $[2] = arr2; - $[3] = t1; - $[4] = getVal1; + $[1] = arr2; + $[2] = foo; + $[3] = getVal1; + $[4] = t1; } else { - t1 = $[3]; - getVal1 = $[4]; + getVal1 = $[3]; + t1 = $[4]; } const getVal2 = t1; let t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md index 3a7016f803..f7353ddd5e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-alloc.expect.md @@ -44,10 +44,10 @@ function Component(t0) { t3 = $[1]; } let t4; - if ($[2] !== t3 || $[3] !== propA) { + if ($[2] !== propA || $[3] !== t3) { t4 = { value: t3, other: propA }; - $[2] = t3; - $[3] = propA; + $[2] = propA; + $[3] = t3; $[4] = t4; } else { t4 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md index f0ce9b9114..c6ad9bcdac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-conditional-access-noAlloc.expect.md @@ -34,10 +34,10 @@ function Component(t0) { const t2 = propB?.x.y; let t3; - if ($[0] !== t2 || $[1] !== propA) { + if ($[0] !== propA || $[1] !== t2) { t3 = { value: t2, other: propA }; - $[0] = t2; - $[1] = propA; + $[0] = propA; + $[1] = t2; $[2] = t3; } else { t3 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md index 9d7feacd6d..7a27bb8521 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-in-other-reactive-block.expect.md @@ -40,7 +40,7 @@ function useFoo(minWidth, otherProp) { const $ = _c(6); const [width] = useState(1); let t0; - if ($[0] !== width || $[1] !== minWidth || $[2] !== otherProp) { + if ($[0] !== minWidth || $[1] !== otherProp || $[2] !== width) { const x = []; let t1; @@ -58,9 +58,9 @@ function useFoo(minWidth, otherProp) { arrayPush(x, otherProp); t0 = [style, x]; - $[0] = width; - $[1] = minWidth; - $[2] = otherProp; + $[0] = minWidth; + $[1] = otherProp; + $[2] = width; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index e9d2bffb30..5fc0ec510b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -44,7 +44,7 @@ function Foo(t0) { const { arr1, arr2, foo } = t0; let t1; let val1; - if ($[0] !== arr1 || $[1] !== foo || $[2] !== arr2) { + if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) { const x = [arr1]; let y; @@ -63,8 +63,8 @@ function Foo(t0) { foo ? (y = x.concat(arr2)) : y; t1 = (() => [y])(); $[0] = arr1; - $[1] = foo; - $[2] = arr2; + $[1] = arr2; + $[2] = foo; $[3] = t1; $[4] = val1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md index 594e68f24f..de39fc9706 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/primitive-reassigned-loop-force-scopes-enabled.expect.md @@ -32,15 +32,15 @@ function Component(t0) { const $ = _c(5); const { base, start, increment, test } = t0; let value; - if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) { + if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) { value = base; for (let i = start; i < test; i = i + increment, i) { value = value + i; } $[0] = base; - $[1] = start; - $[2] = test; - $[3] = increment; + $[1] = increment; + $[2] = start; + $[3] = test; $[4] = value; } else { value = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md index d0486cd8c2..28612f2d73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-as-memo-dep.expect.md @@ -63,10 +63,10 @@ function Component(t0) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== data) { + if ($[4] !== data || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = data; + $[4] = data; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md index b4a55fcb61..2861ab71c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single-with-unconditional.expect.md @@ -45,10 +45,10 @@ function Component(props) { t1 = $[3]; } let t2; - if ($[4] !== t1 || $[5] !== data) { + if ($[4] !== data || $[5] !== t1) { t2 = ; - $[4] = t1; - $[5] = data; + $[4] = data; + $[5] = t1; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md index f15b9b8e9b..b5db44aa2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/optional-member-expression-single.expect.md @@ -60,10 +60,10 @@ function Component(t0) { t3 = $[3]; } let t4; - if ($[4] !== t3 || $[5] !== data) { + if ($[4] !== data || $[5] !== t3) { t4 = ; - $[4] = t3; - $[5] = data; + $[4] = data; + $[5] = t3; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md index 324eb714fc..acc72529ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/partial-early-return-within-reactive-scope.expect.md @@ -32,8 +32,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -60,11 +60,11 @@ function Component(props) { $[0] = props.cond; $[1] = props.a; $[2] = props.b; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; 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 index c0f8aa97cd..673dd0d5fd 100644 --- 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 @@ -58,7 +58,7 @@ function useFoo(t0) { local = $[1]; } let t1; - if ($[2] !== shouldReadA || $[3] !== local) { + if ($[2] !== local || $[3] !== shouldReadA) { t1 = ( { @@ -70,8 +70,8 @@ function useFoo(t0) { shouldInvokeFns={true} /> ); - $[2] = shouldReadA; - $[3] = local; + $[2] = local; + $[3] = shouldReadA; $[4] = t1; } else { t1 = $[4]; 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 index e37b8365a2..abf4c98f23 100644 --- 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 @@ -41,7 +41,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a) { + if ($[0] !== a || $[1] !== shouldReadA) { t1 = ( { @@ -53,8 +53,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a; + $[0] = a; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; 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 index 89b4d281f8..d82956e4a0 100644 --- 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 @@ -51,13 +51,13 @@ function Foo(t0) { const fn = t1; useIdentity(null); let x; - if ($[2] !== cond || $[3] !== a.b.c) { + if ($[2] !== a.b.c || $[3] !== cond) { x = makeArray(); if (cond) { x.push(identity(a.b.c)); } - $[2] = cond; - $[3] = a.b.c; + $[2] = a.b.c; + $[3] = cond; $[4] = x; } else { x = $[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 index 591e04de7b..c81e59ecea 100644 --- 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 @@ -50,22 +50,22 @@ function Foo(t0) { const fn = t1; useIdentity(null); let arr; - if ($[2] !== cond || $[3] !== a.b?.c.e) { + if ($[2] !== a.b?.c.e || $[3] !== cond) { arr = makeArray(); if (cond) { arr.push(identity(a.b?.c.e)); } - $[2] = cond; - $[3] = a.b?.c.e; + $[2] = a.b?.c.e; + $[3] = cond; $[4] = arr; } else { arr = $[4]; } let t2; - if ($[5] !== fn || $[6] !== arr) { + if ($[5] !== arr || $[6] !== fn) { t2 = ; - $[5] = fn; - $[6] = arr; + $[5] = arr; + $[6] = fn; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md index 902a1578c8..30cea1e2c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/promote-uncond.expect.md @@ -38,15 +38,15 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(4); let x; - if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { + if ($[0] !== other || $[1] !== props.a.a.a || $[2] !== props.a.b) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a.a.a; - $[1] = props.a.b; - $[2] = other; + $[0] = other; + $[1] = props.a.a.a; + $[2] = props.a.b; $[3] = x; } else { x = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md index 37846215b1..fee31c9faf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch-non-final-default.expect.md @@ -35,8 +35,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { @@ -65,19 +65,19 @@ function Component(props) { t0 = ; $[0] = props.p0; $[1] = props.p2; - $[2] = y; - $[3] = t0; + $[2] = t0; + $[3] = y; } else { - y = $[2]; - t0 = $[3]; + t0 = $[2]; + y = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md index 1be4143849..6290668015 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/switch.expect.md @@ -30,8 +30,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR function Component(props) { const $ = _c(8); - let y; let t0; + let y; if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { @@ -48,19 +48,19 @@ function Component(props) { $[0] = props.p0; $[1] = props.p2; $[2] = props.p3; - $[3] = y; - $[4] = t0; + $[3] = t0; + $[4] = y; } else { - y = $[3]; - t0 = $[4]; + t0 = $[3]; + y = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[5] !== y || $[6] !== child) { + if ($[5] !== child || $[6] !== y) { t1 = {child}; - $[5] = y; - $[6] = child; + $[5] = child; + $[6] = y; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md index 7480362a43..c6331bd4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md @@ -54,10 +54,10 @@ function Component(props) { } const c = t0; let t1; - if ($[3] !== c || $[4] !== a) { + if ($[3] !== a || $[4] !== c) { t1 = [c, a]; - $[3] = c; - $[4] = a; + $[3] = a; + $[4] = c; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md index 29e3e2757f..35637b1a59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-computed-load.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const count = t1; let t2; - if ($[5] !== items || $[6] !== count) { + if ($[5] !== count || $[6] !== items) { t2 = { items, count }; - $[5] = items; - $[6] = count; + $[5] = count; + $[6] = items; $[7] = t2; } else { t2 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md index 3408f707d6..bb76df4de0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactivity-analysis-reactive-via-mutation-of-property-load.expect.md @@ -40,10 +40,10 @@ function Component(props) { } const count = t1; let t2; - if ($[4] !== items || $[5] !== count) { + if ($[4] !== count || $[5] !== items) { t2 = { items, count }; - $[4] = items; - $[5] = count; + $[4] = count; + $[5] = items; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md index e682a01086..422f4cf547 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassignment-separate-scopes.expect.md @@ -42,8 +42,8 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(10); - let x; let t0; + let x; if ($[0] !== a) { x = []; if (a) { @@ -52,11 +52,11 @@ function foo(a, b, c) { t0 =
{x}
; $[0] = a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const y = t0; bb0: switch (b) { @@ -83,15 +83,15 @@ function foo(a, b, c) { } } let t1; - if ($[7] !== y || $[8] !== x) { + if ($[7] !== x || $[8] !== y) { t1 = (
{y} {x}
); - $[7] = y; - $[8] = x; + $[7] = x; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md index 3ad544344d..f3ddf57ec0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md @@ -34,7 +34,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md index 449aa6d3d8..5700634449 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md @@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; 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 index 4d45d3f3c6..18e9faf63b 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== a.b.c || $[1] !== shouldReadA) { t1 = ( { @@ -50,8 +50,8 @@ function Foo(t0) { shouldInvokeFns={true} /> ); - $[0] = shouldReadA; - $[1] = a.b.c; + $[0] = a.b.c; + $[1] = shouldReadA; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md index 701702f9dd..2dd9362404 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/hoist-deps-diff-ssa-instance.expect.md @@ -80,10 +80,10 @@ function useFoo(t0) { x = $[6]; } let t1; - if ($[7] !== y || $[8] !== x.a.b) { + if ($[7] !== x.a.b || $[8] !== y) { t1 = [y, x.a.b]; - $[7] = y; - $[8] = x.a.b; + $[7] = x.a.b; + $[8] = y; $[9] = t1; } else { t1 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md index b4c25d5f45..e024bc893a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; bb0: { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md index 359ba0dcde..055d1ce12e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/loop-break-in-scope.expect.md @@ -36,7 +36,7 @@ function useFoo(t0) { const $ = _c(3); const { obj, objIsNull } = t0; let x; - if ($[0] !== objIsNull || $[1] !== obj) { + if ($[0] !== obj || $[1] !== objIsNull) { x = []; for (let i = 0; i < 5; i++) { if (objIsNull) { @@ -45,8 +45,8 @@ function useFoo(t0) { x.push(obj.a); } - $[0] = objIsNull; - $[1] = obj; + $[0] = obj; + $[1] = objIsNull; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md index bb47587687..1c2162352b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps.expect.md @@ -42,8 +42,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -77,11 +77,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md index 59225b5155..ca8228e2db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/reduce-if-nonexhaustive-poisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond, hasAB } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== hasAB || $[2] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond; $[1] = hasAB; $[2] = input; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md index 7a7f9a4b6e..ce12fb17b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-in-scope.expect.md @@ -33,9 +33,9 @@ import { c as _c } from "react/compiler-runtime"; function useFoo(t0) { const $ = _c(4); const { obj, objIsNull } = t0; - let x; let t1; - if ($[0] !== objIsNull || $[1] !== obj) { + let x; + if ($[0] !== obj || $[1] !== objIsNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -46,13 +46,13 @@ function useFoo(t0) { x.push(obj.b); } - $[0] = objIsNull; - $[1] = obj; - $[2] = x; - $[3] = t1; + $[0] = obj; + $[1] = objIsNull; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md index 2b925c2479..058bf85b69 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-poisoned/return-poisons-outer-scope.expect.md @@ -39,8 +39,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { input, cond } = t0; - let x; let t1; + let x; if ($[0] !== cond || $[1] !== input) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -61,11 +61,11 @@ function useFoo(t0) { } $[0] = cond; $[1] = input; - $[2] = x; - $[3] = t1; + $[2] = t1; + $[3] = x; } else { - x = $[2]; - t1 = $[3]; + t1 = $[2]; + x = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md index 291998c8ad..2715a0c92d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/jump-target-within-scope-loop-break.expect.md @@ -40,7 +40,7 @@ function useFoo(t0) { const $ = _c(3); const { input, max } = t0; let x; - if ($[0] !== max || $[1] !== input.a.b) { + if ($[0] !== input.a.b || $[1] !== max) { x = []; let i = 0; while (true) { @@ -52,8 +52,8 @@ function useFoo(t0) { x.push(i); x.push(input.a.b); - $[0] = max; - $[1] = input.a.b; + $[0] = input.a.b; + $[1] = max; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md index 84b8fa1d43..845ea4b5ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps.expect.md @@ -33,8 +33,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(9); const { input, hasAB, returnNull } = t0; - let x; let t1; + let x; if ($[0] !== hasAB || $[1] !== input.a || $[2] !== returnNull) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -68,11 +68,11 @@ function useFoo(t0) { $[0] = hasAB; $[1] = input.a; $[2] = returnNull; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md index a55922f610..d3e463495b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/jump-unpoisoned/reduce-if-exhaustive-nonpoisoned-deps1.expect.md @@ -43,8 +43,8 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, cond2, cond1 } = t0; - let x; let t1; + let x; if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== input.a.b) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -88,11 +88,11 @@ function useFoo(t0) { $[0] = cond1; $[1] = cond2; $[2] = input.a.b; - $[3] = x; - $[4] = t1; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..d3a61a1019 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -36,14 +36,14 @@ import { identity } from "shared-runtime"; function usePromoteUnconditionalAccessToDependency(props, other) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== other || $[1] !== props.a) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; + $[0] = other; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md index 01ab4f6b0a..41a7a25d63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/reduce-if-exhaustive-poisoned-deps.expect.md @@ -34,9 +34,9 @@ import { identity } from "shared-runtime"; function useFoo(t0) { const $ = _c(11); const { input, inputHasAB, inputHasABC } = t0; - let x; let t1; - if ($[0] !== inputHasABC || $[1] !== input.a || $[2] !== inputHasAB) { + let x; + if ($[0] !== input.a || $[1] !== inputHasAB || $[2] !== inputHasABC) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { x = []; @@ -75,14 +75,14 @@ function useFoo(t0) { x.push(t2); } } - $[0] = inputHasABC; - $[1] = input.a; - $[2] = inputHasAB; - $[3] = x; - $[4] = t1; + $[0] = input.a; + $[1] = inputHasAB; + $[2] = inputHasABC; + $[3] = t1; + $[4] = x; } else { - x = $[3]; - t1 = $[4]; + t1 = $[3]; + x = $[4]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md index a62223d72d..03e3e96397 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md @@ -40,14 +40,14 @@ import { identity } from "shared-runtime"; function useConditionalSubpath1(props, cond) { const $ = _c(3); let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.b = props.a.b; if (identity(cond)) { x.a = props.a; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md index 02117feee2..5b246175f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md @@ -48,14 +48,14 @@ function useConditionalSuperpath1(t0) { const $ = _c(3); const { props, cond } = t0; let x; - if ($[0] !== props.a || $[1] !== cond) { + if ($[0] !== cond || $[1] !== props.a) { x = {}; x.a = props.a; if (identity(cond)) { x.b = props.a.b; } - $[0] = props.a; - $[1] = cond; + $[0] = cond; + $[1] = props.a; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md index 34979e9de9..c91cf94445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/uncond-access-in-mutable-range.expect.md @@ -49,15 +49,15 @@ function Component(t0) { const $ = _c(8); const { cond, other } = t0; let x; - if ($[0] !== other || $[1] !== cond) { + if ($[0] !== cond || $[1] !== other) { x = makeObject_Primitives(); setProperty(x, { b: 3, other }, "a"); identity(x.a.b); if (!cond) { x.a = null; } - $[0] = other; - $[1] = cond; + $[0] = cond; + $[1] = other; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md index da6912d35e..a2bd99e9a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reordering-across-blocks.expect.md @@ -82,10 +82,10 @@ function Component(t0) { } const b = t3; let t4; - if ($[4] !== b || $[5] !== a) { + if ($[4] !== a || $[5] !== b) { t4 = { b, a }; - $[4] = b; - $[5] = a; + $[4] = a; + $[5] = b; $[6] = t4; } else { t4 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md index bba0b3f139..0089d20af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-independently-memoized-property-load-for-method-call.expect.md @@ -59,7 +59,7 @@ function Component(t0) { const serverTime = useServerTime(); let t1; let timestampLabel; - if ($[0] !== highlightedItem || $[1] !== serverTime || $[2] !== label) { + if ($[0] !== highlightedItem || $[1] !== label || $[2] !== serverTime) { const highlight = new Highlight(highlightedItem); const time = serverTime.get(); @@ -68,8 +68,8 @@ function Component(t0) { t1 = highlight.render(); $[0] = highlightedItem; - $[1] = serverTime; - $[2] = label; + $[1] = label; + $[2] = serverTime; $[3] = t1; $[4] = timestampLabel; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md index 9c21dc8400..ecdfa88b98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value-via-alias.expect.md @@ -66,10 +66,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md index 1b6e91a6fd..f9d725e0b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-pruned-scope-leaks-value.expect.md @@ -65,10 +65,10 @@ function MyApp(t0) { const z = makeObject_Primitives(); const x = useIdentity(2); let t1; - if ($[0] !== x || $[1] !== count) { + if ($[0] !== count || $[1] !== x) { t1 = sum(x, count); - $[0] = x; - $[1] = count; + $[0] = count; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md index 2dabc256f9..1ad6f6a047 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-invalid-reactivity-value-block.expect.md @@ -71,10 +71,10 @@ function Foo() { const shouldCaptureObj = obj != null && CONST_TRUE; const t0 = shouldCaptureObj ? identity(obj) : null; let t1; - if ($[0] !== t0 || $[1] !== obj) { + if ($[0] !== obj || $[1] !== t0) { t1 = [t0, obj]; - $[0] = t0; - $[1] = obj; + $[0] = obj; + $[1] = t0; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md index 4921fd340b..d35cbe266f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types-explicit-types.expect.md @@ -77,15 +77,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md index 80d046ec18..eda7a0cbfe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-missing-memoization-lack-of-phi-types.expect.md @@ -74,15 +74,15 @@ function Component() { const map = t3; const index = filtered.findIndex(_temp3); let t5; - if ($[8] !== map || $[9] !== index) { + if ($[8] !== index || $[9] !== map) { t5 = (
{map} {index}
); - $[8] = map; - $[9] = index; + $[8] = index; + $[9] = map; $[10] = t5; } else { t5 = $[10]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md index 5eff1aa0fe..b5a7827728 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md @@ -21,13 +21,13 @@ function Component(listItem, thread) { let t0; let t1; let t2; - if ($[0] !== thread.threadType || $[1] !== listItem) { + if ($[0] !== listItem || $[1] !== thread.threadType) { const isFoo = isFooThread(thread.threadType); t1 = useBar; t2 = listItem; t0 = getBadgeText(listItem, isFoo); - $[0] = thread.threadType; - $[1] = listItem; + $[0] = listItem; + $[1] = thread.threadType; $[2] = t0; $[3] = t1; $[4] = t2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md index 1d4a4b5d67..32cbbb2b91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-propagate-type-of-ternary-jsx.expect.md @@ -28,7 +28,7 @@ function V0(t0) { const { v1, v2 } = t0; const v5 = v1.v6?.v7; let t1; - if ($[0] !== v5 || $[1] !== v1 || $[2] !== v2) { + if ($[0] !== v1 || $[1] !== v2 || $[2] !== v5) { t1 = ( {v5 != null ? ( @@ -40,9 +40,9 @@ function V0(t0) { )} ); - $[0] = v5; - $[1] = v1; - $[2] = v2; + $[0] = v1; + $[1] = v2; + $[2] = v5; $[3] = t1; } else { t1 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md index cec64e8cf0..ab409b366d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-slow-validate-preserve-memo.expect.md @@ -36,7 +36,7 @@ function useTest(t0) { const $ = _c(3); const { isNull, data } = t0; let t1; - if ($[0] !== isNull || $[1] !== data) { + if ($[0] !== data || $[1] !== isNull) { t1 = Builder.makeBuilder(isNull, "hello world") ?.push("1", 2) ?.push(3, { a: 4, b: 5, c: data }) @@ -47,8 +47,8 @@ function useTest(t0) { ) ?.push(7, "8") ?.push("8", Builder.makeBuilder(!isNull)?.push(9).vals)?.vals; - $[0] = isNull; - $[1] = data; + $[0] = data; + $[1] = isNull; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md index 73674e5fff..496d80d52b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-unreachable-code-early-return-in-useMemo.expect.md @@ -77,10 +77,10 @@ function Component(t0) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== result) { + if ($[4] !== result || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = result; + $[4] = result; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md index 2e9daceed7..1b49552bea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro.expect.md @@ -26,8 +26,8 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); const item = props.item; - let t0; let baseVideos; + let t0; let thumbnails; if ($[0] !== item) { thumbnails = []; @@ -40,12 +40,12 @@ function Component(props) { } }); $[0] = item; - $[1] = t0; - $[2] = baseVideos; + $[1] = baseVideos; + $[2] = t0; $[3] = thumbnails; } else { - t0 = $[1]; - baseVideos = $[2]; + baseVideos = $[1]; + t0 = $[2]; thumbnails = $[3]; } t0 = undefined; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md index 9aa75b2162..aece9665c9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-array-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const [bar] = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md index ded1eb4006..3681e9c469 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-identifier.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const bar = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md index f0a46be168..3e66b20041 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rest-param-with-object-spread-pattern.expect.md @@ -21,10 +21,10 @@ function Component(foo, ...t0) { const $ = _c(3); const { bar } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = [foo, bar]; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md index 7c9c6a5028..1201a9a737 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare-maybe-frozen.expect.md @@ -73,14 +73,14 @@ function foo(props) { } const header = t0; let y; - if ($[5] !== x || $[6] !== props.b || $[7] !== props.c) { + if ($[5] !== props.b || $[6] !== props.c || $[7] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[5] = x; - $[6] = props.b; - $[7] = props.c; + $[5] = props.b; + $[6] = props.c; + $[7] = x; $[8] = y; $[9] = x; } else { @@ -103,15 +103,15 @@ function foo(props) { } const content = t1; let t2; - if ($[13] !== header || $[14] !== content) { + if ($[13] !== content || $[14] !== header) { t2 = ( <> {header} {content} ); - $[13] = header; - $[14] = content; + $[13] = content; + $[14] = header; $[15] = t2; } else { t2 = $[15]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md index 36b1d4541a..143496678e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/same-variable-as-dep-and-redeclare.expect.md @@ -53,30 +53,30 @@ import { c as _c } from "react/compiler-runtime"; // note: comments are for the // emitted function foo(props) { const $ = _c(14); - let x; let t0; + let x; if ($[0] !== props.a) { x = []; x.push(props.a); t0 =
{x}
; $[0] = props.a; - $[1] = x; - $[2] = t0; + $[1] = t0; + $[2] = x; } else { - x = $[1]; - t0 = $[2]; + t0 = $[1]; + x = $[2]; } const header = t0; let y; - if ($[3] !== x || $[4] !== props.b || $[5] !== props.c) { + if ($[3] !== props.b || $[4] !== props.c || $[5] !== x) { y = [x]; x = []; y.push(props.b); x.push(props.c); - $[3] = x; - $[4] = props.b; - $[5] = props.c; + $[3] = props.b; + $[4] = props.c; + $[5] = x; $[6] = y; $[7] = x; } else { @@ -99,15 +99,15 @@ function foo(props) { } const content = t1; let t2; - if ($[11] !== header || $[12] !== content) { + if ($[11] !== content || $[12] !== header) { t2 = ( <> {header} {content} ); - $[11] = header; - $[12] = content; + $[11] = content; + $[12] = header; $[13] = t2; } else { t2 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 5f10f44202..36a68d07c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -43,9 +43,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); - let text; let t0; let t1; + let text; if ($[0] !== statusName) { const { status, text: t2 } = foo(statusName); text = t2; @@ -54,13 +54,13 @@ function Component(statusName) { t1 = identity(bg); t0 = identity(color); $[0] = statusName; - $[1] = text; - $[2] = t0; - $[3] = t1; + $[1] = t0; + $[2] = t1; + $[3] = text; } else { - text = $[1]; - t0 = $[2]; - t1 = $[3]; + t0 = $[1]; + t1 = $[2]; + text = $[3]; } let t2; if ($[4] !== text) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index e2cd53bd0d..d8e991dc46 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -46,9 +46,9 @@ import { identity } from "shared-runtime"; function Component(statusName) { const $ = _c(12); + let font; let t0; let text; - let font; if ($[0] !== statusName) { const { status, text: t1 } = foo(statusName); text = t1; @@ -58,13 +58,13 @@ function Component(statusName) { t0 = identity(color); $[0] = statusName; - $[1] = t0; - $[2] = text; - $[3] = font; + $[1] = font; + $[2] = t0; + $[3] = text; } else { - t0 = $[1]; - text = $[2]; - font = $[3]; + font = $[1]; + t0 = $[2]; + text = $[3]; } const bg = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..788109636b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -34,8 +34,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(7); - let y; let t0; + let y; if ($[0] !== props) { const x = []; bb0: switch (props.p0) { @@ -63,19 +63,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[4] !== child || $[5] !== y) { t1 = {child}; - $[4] = y; - $[5] = child; + $[4] = child; + $[5] = y; $[6] = t1; } else { t1 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..2628982655 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -29,8 +29,8 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(6); - let y; let t0; + let y; if ($[0] !== props) { const x = []; switch (props.p0) { @@ -45,19 +45,19 @@ function Component(props) { t0 = ; $[0] = props; - $[1] = y; - $[2] = t0; + $[1] = t0; + $[2] = y; } else { - y = $[1]; - t0 = $[2]; + t0 = $[1]; + y = $[2]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[3] !== child || $[4] !== y) { t1 = {child}; - $[3] = y; - $[4] = child; + $[3] = child; + $[4] = y; $[5] = t1; } else { t1 = $[5]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md index f106382d64..4864c51a1f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md @@ -50,7 +50,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -64,8 +64,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -85,15 +85,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md index 77fd38aea1..c661094fdd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md @@ -55,7 +55,7 @@ function Component(t0) { const { arr } = t0; const x = useX(); let t1; - if ($[0] !== x || $[1] !== arr) { + if ($[0] !== arr || $[1] !== x) { let t2; if ($[3] !== x) { t2 = (i, id) => ( @@ -70,8 +70,8 @@ function Component(t0) { t2 = $[4]; } t1 = arr.map(t2); - $[0] = x; - $[1] = arr; + $[0] = arr; + $[1] = x; $[2] = t1; } else { t1 = $[2]; @@ -91,15 +91,15 @@ function Bar(t0) { const $ = _c(3); const { x, children } = t0; let t1; - if ($[0] !== x || $[1] !== children) { + if ($[0] !== children || $[1] !== x) { t1 = ( <> {x} {children} ); - $[0] = x; - $[1] = children; + $[0] = children; + $[1] = x; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md index d064a48b71..7ac6486b47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-array-destructuring.expect.md @@ -18,10 +18,10 @@ function App() { const $ = _c(3); const [foo, bar] = useContext(MyContext); let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md index f82af06866..3eac66304b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-destructure-multiple.expect.md @@ -22,10 +22,10 @@ function App() { const { foo } = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md index 573b6db231..4cca8b19d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-mixed-array-obj.expect.md @@ -22,10 +22,10 @@ function App() { const [foo] = context; const { bar } = context; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md index 03ce7f97ba..f5a3916626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-nested-destructuring.expect.md @@ -22,10 +22,10 @@ function App() { const { joe: t0, bar } = useContext(MyContext); const { foo } = t0; let t1; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t1 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md index 55387503cf..0888d67b2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.lower-context-access-property-load.expect.md @@ -22,10 +22,10 @@ function App() { const foo = context.foo; const bar = context.bar; let t0; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { t0 = ; - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 268fa8d7eb..2c27360c9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -42,9 +42,9 @@ import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { const $ = _c(6); const { value, cond } = t0; - let y; let t1; - if ($[0] !== value || $[1] !== cond) { + let y; + if ($[0] !== cond || $[1] !== value) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { y = [value]; @@ -68,13 +68,13 @@ function useFoo(t0) { } y.push(x); } - $[0] = value; - $[1] = cond; - $[2] = y; - $[3] = t1; + $[0] = cond; + $[1] = value; + $[2] = t1; + $[3] = y; } else { - y = $[2]; - t1 = $[3]; + t1 = $[2]; + y = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index b04bd53458..562c0bc1c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -32,8 +32,8 @@ const { throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -47,11 +47,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md index af5f2ebfcf..71a59aba2f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-with-return.expect.md @@ -33,8 +33,8 @@ const { shallowCopy, throwInput } = require("shared-runtime"); function Component(props) { const $ = _c(2); - let x; let t0; + let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { @@ -52,11 +52,11 @@ function Component(props) { break bb0; } } - $[0] = x; - $[1] = t0; + $[0] = t0; + $[1] = x; } else { - x = $[0]; - t0 = $[1]; + t0 = $[0]; + x = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md index 54d5be2d6b..c3c45beb86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log-default-import.expect.md @@ -78,10 +78,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -95,10 +95,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md index 072c6d03d9..4acbd2dfdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-log.expect.md @@ -76,10 +76,10 @@ export function Component(t0) { t5 = $[5]; } let t6; - if ($[6] !== t5 || $[7] !== item1) { + if ($[6] !== item1 || $[7] !== t5) { t6 = ; - $[6] = t5; - $[7] = item1; + $[6] = item1; + $[7] = t5; $[8] = t6; } else { t6 = $[8]; @@ -93,10 +93,10 @@ export function Component(t0) { t7 = $[10]; } let t8; - if ($[11] !== t7 || $[12] !== item2) { + if ($[11] !== item2 || $[12] !== t7) { t8 = ; - $[11] = t7; - $[12] = item2; + $[11] = item2; + $[12] = t7; $[13] = t8; } else { t8 = $[13]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md index caa74267f3..5016b0c4df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture-namespace-import.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md index a92abd4ca5..3f341fc665 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-provider-store-capture.expect.md @@ -114,10 +114,10 @@ export function Component(t0) { } const t10 = items_0[1]; let t11; - if ($[14] !== t9 || $[15] !== t10) { + if ($[14] !== t10 || $[15] !== t9) { t11 = ; - $[14] = t9; - $[15] = t10; + $[14] = t10; + $[15] = t9; $[16] = t11; } else { t11 = $[16]; @@ -132,16 +132,16 @@ export function Component(t0) { t12 = $[19]; } let t13; - if ($[20] !== t12 || $[21] !== items_0) { + if ($[20] !== items_0 || $[21] !== t12) { t13 = ; - $[20] = t12; - $[21] = items_0; + $[20] = items_0; + $[21] = t12; $[22] = t13; } else { t13 = $[22]; } let t14; - if ($[23] !== t8 || $[24] !== t11 || $[25] !== t13) { + if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) { t14 = ( <> {t8} @@ -149,9 +149,9 @@ export function Component(t0) { {t13} ); - $[23] = t8; - $[24] = t11; - $[25] = t13; + $[23] = t11; + $[24] = t13; + $[25] = t8; $[26] = t14; } else { t14 = $[26]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index fbd13dc1b0..7fa86838e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -38,22 +38,22 @@ function component(a) { const f = typeof t.t; let t0; if ( - $[0] !== z || - $[1] !== p || - $[2] !== q || + $[0] !== e || + $[1] !== f || + $[2] !== m || $[3] !== n || - $[4] !== m || - $[5] !== e || - $[6] !== f + $[4] !== p || + $[5] !== q || + $[6] !== z ) { t0 = { z, p, q, n, m, e, f }; - $[0] = z; - $[1] = p; - $[2] = q; + $[0] = e; + $[1] = f; + $[2] = m; $[3] = n; - $[4] = m; - $[5] = e; - $[6] = f; + $[4] = p; + $[5] = q; + $[6] = z; $[7] = t0; } else { t0 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md index 663a6788f3..7f79cae4a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-call-expression.expect.md @@ -89,10 +89,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..d94a5e7e37 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -109,10 +109,10 @@ function Inner(props) { t4 = $[3]; } let t5; - if ($[4] !== t4 || $[5] !== output) { + if ($[4] !== output || $[5] !== t4) { t5 = ; - $[4] = t4; - $[5] = output; + $[4] = output; + $[5] = t4; $[6] = t5; } else { t5 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md index 99effce934..cf0093ea94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-method-call.expect.md @@ -91,10 +91,10 @@ function Inner(props) { t2 = $[3]; } let t3; - if ($[4] !== t2 || $[5] !== output) { + if ($[4] !== output || $[5] !== t2) { t3 = ; - $[4] = t2; - $[5] = output; + $[4] = output; + $[5] = t2; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md index 1292ea1489..c3e115fa0d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-nested-lambdas.expect.md @@ -51,7 +51,7 @@ function Component(props) { } const exit = t0; let t1; - if ($[2] !== item.value || $[3] !== exit) { + if ($[2] !== exit || $[3] !== item.value) { t1 = () => { const cleanup = GlobalEventEmitter.addListener("onInput", () => { if (item.value) { @@ -60,8 +60,8 @@ function Component(props) { }); return () => cleanup.remove(); }; - $[2] = item.value; - $[3] = exit; + $[2] = exit; + $[3] = item.value; $[4] = t1; } else { t1 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md index b6a4187355..28d3e33359 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useState-unpruned-dependency.expect.md @@ -62,13 +62,13 @@ function Component(props) { {w} ); - let condition = $[2] !== x || $[3] !== w; + let condition = $[2] !== w || $[3] !== x; if (!condition) { let old$t1 = $[4]; $structuralCheck(old$t1, t1, "t1", "Component", "cached", "(7:10)"); } - $[2] = x; - $[3] = w; + $[2] = w; + $[3] = x; $[4] = t1; if (condition) { t1 = ( From fde153bb90411853fa53206b595db5970a464191 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 25 Oct 2024 11:36:53 -0700 Subject: [PATCH 54/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. This stack was tested by syncing internally to Meta and spot checking ~40 files for differences in compilation output. Similar to snap fixtures, we see more granular memo dependencies and more outlining. ([meta internal link](https://www.internalfb.com/intern/paste/P1667309207/)) ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 2 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 61 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 129 insertions(+), 336 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index a772be62aa..d10b74f661 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 31e42049fd..4c57e792e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -230,8 +230,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 263ec4c208..506db0e66e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..c030a90cea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,23 +58,7 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } case 'LoadLocal': @@ -115,11 +84,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +96,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +113,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +140,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input; From 7ac7b52f6c1500da5c5b7cbccc95a0cac9ce86ec Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 55/63] [compiler][ez] Patch hoistability for ObjectMethods Extends #31066 to ObjectMethods (somehow missed this before). ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 8 +- .../infer-objectmethod-cond-access.expect.md | 82 +++++++++++++++++++ .../infer-objectmethod-cond-access.js | 26 ++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js 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 80593d6275..d2e7220159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -348,7 +348,8 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(maybeNonNull); } if ( - instr.value.kind === 'FunctionExpression' && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; @@ -591,7 +592,10 @@ function collectFunctionExpressionFakeLoads( for (const [_, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { - if (value.kind === 'FunctionExpression') { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { for (const reference of value.loweredFunc.dependencies) { let curr: IdentifierId | undefined = reference.identifier.id; while (curr != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md new file mode 100644 index 0000000000..2c7bf6bbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.expect.md @@ -0,0 +1,82 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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 = ( + + ); + $[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') ]] +
{"objectMethod":{"method":{"kind":"Function","result":null}},"shouldInvokeFns":true}
+
{"objectMethod":{"method":{"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-objectmethod-cond-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js new file mode 100644 index 0000000000..2c8488bb29 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/infer-objectmethod-cond-access.js @@ -0,0 +1,26 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({a, shouldReadA}) { + return ( + + ); +} + +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}, + ], +}; From e288deabd71d00290ae675452f0e1d62aa2de6e9 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 56/63] [compiler] bugfix for hoistable deps for nested functions `PropertyPathRegistry` is responsible for uniqueing identifier and property paths. This is necessary for the hoistability CFG merging logic which takes unions and intersections of these nodes to determine a basic block's hoistable reads, as a function of its neighbors. We also depend on this to merge optional chained and non-optional chained property paths This fixes a small bug in #31066 in which we create a new registry for nested functions. Now, we use the same registry for a component / hook and all its inner functions ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 100 +++++++++++------- .../src/HIR/PropagateScopeDependenciesHIR.ts | 2 +- .../repro-invariant.expect.md | 61 +++++++++++ .../repro-invariant.tsx | 14 +++ 4 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx 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 d2e7220159..456425aeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -1,5 +1,6 @@ import {CompilerError} from '../CompilerError'; import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; import { Set_equal, Set_filter, @@ -23,6 +24,8 @@ import { } from './HIR'; import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; +const DEBUG_PRINT = false; + /** * Helper function for `PropagateScopeDependencies`. Uses control flow graph * analysis to determine which `Identifier`s can be assumed to be non-null @@ -86,15 +89,8 @@ export function collectHoistablePropertyLoads( fn: HIRFunction, temporaries: ReadonlyMap, hoistableFromOptionals: ReadonlyMap, - nestedFnImmutableContext: ReadonlySet | null, ): ReadonlyMap { const registry = new PropertyPathRegistry(); - - 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 @@ -111,14 +107,51 @@ export function collectHoistablePropertyLoads( } } } - const nodes = collectNonNullsInBlocks(fn, { - temporaries: actuallyEvaluatedTemporaries, + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, knownImmutableIdentifiers, hoistableFromOptionals, registry, - nestedFnImmutableContext, + nestedFnImmutableContext: null, }); - propagateNonNull(fn, nodes, registry); +} + +type CollectHoistablePropertyLoadsContext = { + 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 collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } return nodes; } @@ -243,7 +276,7 @@ class PropertyPathRegistry { function getMaybeNonNullInInstruction( instr: InstructionValue, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): PropertyPathNode | null { let path = null; if (instr.kind === 'PropertyLoad') { @@ -262,7 +295,7 @@ function getMaybeNonNullInInstruction( function isImmutableAtInstr( identifier: Identifier, instr: InstructionId, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): boolean { if (context.nestedFnImmutableContext != null) { /** @@ -295,22 +328,9 @@ function isImmutableAtInstr( } } -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, - context: CollectNonNullsInBlocksContext, + context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { /** * Known non-null objects such as functional component props can be safely @@ -358,18 +378,22 @@ function collectNonNullsInBlocks( new Set(), ); const innerOptionals = collectOptionalChainSidemap(innerFn.func); - const innerHoistableMap = collectHoistablePropertyLoads( + const innerHoistableMap = collectHoistablePropertyLoadsImpl( 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), - ), + { + ...context, + temporaries: innerTemporaries, // TODO: remove in later PR + hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR + nestedFnImmutableContext: + 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), 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 855ca9121d..0178aea6e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -46,7 +46,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void { const hoistablePropertyLoads = keyByScopeId( fn, - collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null), + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), ); const scopeDeps = collectDependencies( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md new file mode 100644 index 0000000000..73df2b615b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR +import { Stringify } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(5); + const { data } = t0; + let t1; + if ($[0] !== data.a.d) { + t1 = () => data.a.d; + $[0] = data.a.d; + $[1] = t1; + } else { + t1 = $[1]; + } + const t2 = data.a?.b.c; + let t3; + if ($[2] !== t1 || $[3] !== t2) { + t3 = ; + $[2] = t1; + $[3] = t2; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ data: { a: null } }], + sequentialRenders: [{ data: { a: { b: { c: 4 } } } }], +}; + +``` + +### Eval output +(kind: ok)
{"foo":{"kind":"Function"},"bar":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/repro-invariant.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx new file mode 100644 index 0000000000..05ed136d5f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/repro-invariant.tsx @@ -0,0 +1,14 @@ +// @enablePropagateDepsInHIR +import {Stringify} from 'shared-runtime'; + +function Foo({data}) { + return ( + data.a.d} bar={data.a?.b.c} shouldInvokeFns={true} /> + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{data: {a: null}}], + sequentialRenders: [{data: {a: {b: {c: 4}}}}], +}; From 19506bc5d8ec02214f2fa40a1283265d9e838ea2 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 57/63] [compiler] Delete propagateScopeDeps (non-hir) `enablePropagateScopeDepsHIR` is now used extensively in Meta. This has been tested for over two weeks in our e2e tests and production. The rest of this stack deletes `LoweredFunction.dependencies`, which the non-hir version of `PropagateScopeDeps` depends on. To avoid a more forked HIR (non-hir with dependencies and hir with no dependencies), let's go ahead and clean up the non-hir version of PropagateScopeDepsHIR. Note that all fixture changes in this PR were previously reviewed when they were copied to `propagate-scope-deps-hir-fork`. Will clean up / merge these duplicate fixtures in a later PR ' --- .../src/Entrypoint/Pipeline.ts | 24 +- .../src/HIR/Environment.ts | 10 - .../PropagateScopeDependencies.ts | 1324 ----------------- .../src/ReactiveScopes/index.ts | 1 - ...ug-invalid-hoisting-functionexpr.expect.md | 4 +- ...-try-catch-maybe-null-dependency.expect.md | 16 +- .../capturing-func-mutate-2.expect.md | 4 +- .../conditional-break-labeled.expect.md | 18 +- .../conditional-early-return.expect.md | 78 +- .../compiler/conditional-on-mutable.expect.md | 24 +- ...rly-return-within-reactive-scope.expect.md | 26 +- ...rly-return-within-reactive-scope.expect.md | 20 +- ...ession-with-conditional-optional.expect.md | 50 + ...r-expression-with-conditional-optional.js} | 0 ...mber-expression-with-conditional.expect.md | 50 + ...nal-member-expression-with-conditional.js} | 0 ...-optional-call-chain-in-optional.expect.md | 2 +- ...unctionexpr-conditional-access-2.expect.md | 4 +- .../functionexpr-conditional-access-2.tsx | 2 +- .../functionexpr–conditional-access.expect.md | 8 +- .../functionexpr–conditional-access.js | 2 +- .../iife-return-modified-later-phi.expect.md | 11 +- ...equential-optional-chain-nonnull.expect.md | 4 +- .../compiler/nested-optional-chains.expect.md | 12 +- ...consequent-alternate-both-return.expect.md | 11 +- ...ession-with-conditional-optional.expect.md | 74 - ...mber-expression-with-conditional.expect.md | 74 - ...rly-return-within-reactive-scope.expect.md | 22 +- ...ence-array-push-consecutive-phis.expect.md | 18 +- .../phi-type-inference-array-push.expect.md | 11 +- ...hi-type-inference-property-store.expect.md | 11 +- ...ack-conditional-access-own-scope.expect.md | 50 + ...eCallback-conditional-access-own-scope.ts} | 0 ...ck-infer-conditional-value-block.expect.md | 59 + ...Callback-infer-conditional-value-block.ts} | 0 ...less-specific-conditional-access.expect.md | 2 + ...ack-conditional-access-own-scope.expect.md | 58 - ...ck-infer-conditional-value-block.expect.md | 63 - ...properties-inside-optional-chain.expect.md | 4 +- ...-in-returned-function-expression.expect.md | 4 +- ...function-cond-access-not-hoisted.expect.md | 4 +- ...e-uncond-optional-chain-and-cond.expect.md | 4 +- .../join-uncond-scopes-cond-deps.expect.md | 15 +- .../promote-uncond.expect.md | 13 +- .../ssa-cascading-eliminated-phis.expect.md | 25 +- .../compiler/ssa-leave-case.expect.md | 11 +- ...ernary-destruction-with-mutation.expect.md | 12 +- ...ssa-renaming-ternary-destruction.expect.md | 11 +- ...a-renaming-ternary-with-mutation.expect.md | 12 +- .../compiler/ssa-renaming-ternary.expect.md | 11 +- ...onditional-ternary-with-mutation.expect.md | 12 +- ...a-renaming-unconditional-ternary.expect.md | 12 +- ...ming-unconditional-with-mutation.expect.md | 12 +- ...-via-destructuring-with-mutation.expect.md | 12 +- .../ssa-renaming-with-mutation.expect.md | 12 +- .../switch-non-final-default.expect.md | 31 +- .../fixtures/compiler/switch.expect.md | 26 +- .../try-catch-mutate-outer-value.expect.md | 16 +- ...-expression-returns-caught-value.expect.md | 4 +- ...ject-method-returns-caught-value.expect.md | 4 +- .../useMemo-multiple-if-else.expect.md | 22 +- 61 files changed, 567 insertions(+), 1869 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional-optional.js => error.hoist-optional-member-expression-with-conditional-optional.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{optional-member-expression-with-conditional.js => error.hoist-optional-member-expression-with-conditional.js} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-conditional-access-own-scope.ts => error.hoist-useCallback-conditional-access-own-scope.ts} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/{useCallback-infer-conditional-value-block.ts => error.hoist-useCallback-infer-conditional-value-block.ts} (100%) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 7ae520a144..1127e91029 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -57,7 +57,6 @@ import { mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -348,14 +347,12 @@ function* runWithEnvironment( }); assertTerminalSuccessorsExist(hir); assertTerminalPredsExist(hir); - if (env.config.enablePropagateDepsInHIR) { - propagateScopeDependenciesHIR(hir); - yield log({ - kind: 'hir', - name: 'PropagateScopeDependenciesHIR', - value: hir, - }); - } + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); @@ -383,15 +380,6 @@ function* runWithEnvironment( }); assertScopeInstructionsWithinScopes(reactiveFunction); - if (!env.config.enablePropagateDepsInHIR) { - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - } - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 3e2b5597ac..ce38e91af4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enablePropagateDepsInHIR: z.boolean().default(false), - - /** - * Enables inference of optional dependency chains. Without this flag - * a property chain such as `props?.items?.foo` will infer as a dep on - * just `props`. With this flag enabled, we'll infer that full path as - * the dependency. - */ - enableOptionalDependencies: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index dc1142b271..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1324 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import {Environment} from '../HIR'; -import { - areEqualPaths, - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveOptionalCallValue, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction(fn, new PropagationVisitor(fn.env), context); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - optional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - }; - } - - objectDependency.path.push({property, optional}); - - return objectDependency; - } - - declareProperty( - lvalue: Place, - object: Place, - property: string, - optional: boolean, - ): void { - const nextDependency = this.#getProperty(object, property, optional); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0)?.property === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string, optional: boolean): void { - const nextDependency = this.#getProperty(object, property, optional); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - env: Environment; - - constructor(env: Environment) { - super(); - this.env = env; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - extractOptionalProperty( - context: Context, - optionalValue: ReactiveOptionalCallValue, - lvalue: Place, - ): { - lvalue: Place; - object: Place; - property: string; - optional: boolean; - } | null { - const sequence = optionalValue.value; - CompilerError.invariant(sequence.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${sequence.kind}\``, - loc: sequence.loc, - }); - /** - * Base case: inner ` "?." ` - *``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = LoadLocal - * Sequence - * t1 = PropertyLoad t0 . - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'LoadLocal' && - sequence.instructions[0].value.place.identifier.name !== null && - !context.isUsedOutsideDeclaringScope(sequence.instructions[0].lvalue) && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.instructions[0].lvalue !== null && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - context.declareTemporary( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.place, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - /** - * Base case 2: inner ` "." "?." - * ``` - * = OptionalExpression optional=true (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t1 = LoadLocal - * ... // see note - * PropertyLoad t1 . - * [46] Sequence - * t2 = PropertyLoad t0 . - * [46] LoadLocal t2 - * ``` - * - * Note that it's possible to have additional inner chained non-optional - * property loads at "...", from an expression like `a?.b.c.d.e`. We could - * expand to support this case by relaxing the check on the inner sequence - * length, ensuring all instructions after the first LoadLocal are PropertyLoad - * and then iterating to ensure that the lvalue of the previous is always - * the object of the next PropertyLoad, w the final lvalue as the object - * of the sequence.value's object. - * - * But this case is likely rare in practice, usually once you're optional - * chaining all property accesses are optional (not `a?.b.c` but `a?.b?.c`). - * Also, HIR-based PropagateScopeDeps will handle this case so it doesn't - * seem worth it to optimize for that edge-case here. - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].lvalue !== null && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'LoadLocal' && - sequence.instructions[0].value.instructions[0].value.place.identifier - .name !== null && - !context.isUsedOutsideDeclaringScope( - sequence.instructions[0].value.instructions[0].lvalue, - ) && - sequence.instructions[0].value.value.kind === 'PropertyLoad' && - sequence.instructions[0].value.value.object.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].lvalue.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - // LoadLocal - context.declareTemporary( - sequence.instructions[0].value.instructions[0].lvalue, - sequence.instructions[0].value.instructions[0].value.place, - ); - // PropertyLoad . (the inner non-optional property) - context.declareProperty( - sequence.instructions[0].lvalue, - sequence.instructions[0].value.value.object, - sequence.instructions[0].value.value.property, - false, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - - /** - * Composed case: - * - ` "." or "?." ` - * - ` "." or "?>" ` - * - * This case is convoluted, note how `t0` appears as an lvalue *twice* - * and then is an operand of an intermediate LoadLocal and then the - * object of the final PropertyLoad: - * - * ``` - * = OptionalExpression optional=false (`optionalValue` is here) - * Sequence (`sequence` is here) - * t0 = Sequence - * t0 = - * - * LoadLocal t0 - * Sequence - * t1 = PropertyLoad t0. - * LoadLocal t1 - * ``` - */ - if ( - sequence.instructions.length === 1 && - sequence.instructions[0].value.kind === 'SequenceExpression' && - sequence.instructions[0].value.instructions.length === 1 && - sequence.instructions[0].value.instructions[0].lvalue !== null && - sequence.instructions[0].value.instructions[0].value.kind === - 'OptionalExpression' && - sequence.instructions[0].value.value.kind === 'LoadLocal' && - sequence.instructions[0].value.value.place.identifier.id === - sequence.instructions[0].value.instructions[0].lvalue.identifier.id && - sequence.value.kind === 'SequenceExpression' && - sequence.value.instructions.length === 1 && - sequence.value.instructions[0].lvalue !== null && - sequence.value.instructions[0].value.kind === 'PropertyLoad' && - sequence.value.instructions[0].value.object.identifier.id === - sequence.instructions[0].value.value.place.identifier.id && - sequence.value.value.kind === 'LoadLocal' && - sequence.value.value.place.identifier.id === - sequence.value.instructions[0].lvalue.identifier.id - ) { - const {lvalue: innerLvalue, value: innerOptional} = - sequence.instructions[0].value.instructions[0]; - const innerProperty = this.extractOptionalProperty( - context, - innerOptional, - innerLvalue, - ); - if (innerProperty === null) { - return null; - } - context.declareProperty( - innerProperty.lvalue, - innerProperty.object, - innerProperty.property, - innerProperty.optional, - ); - const propertyLoad = sequence.value.instructions[0].value; - return { - lvalue, - object: propertyLoad.object, - property: propertyLoad.property, - optional: optionalValue.optional, - }; - } - return null; - } - - visitOptionalExpression( - context: Context, - id: InstructionId, - value: ReactiveOptionalCallValue, - lvalue: Place | null, - ): void { - /** - * If this is the first optional=true optional in a recursive OptionalExpression - * subtree, we check to see if the subtree is of the form: - * ``` - * NestedOptional = - * ` . / ?. ` - * ` . / ?. ` - * ``` - * - * Ie strictly a chain like `foo?.bar?.baz` or `a?.b.c`. If the subtree contains - * any other types of expressions - for example `foo?.[makeKey(a)]` - then this - * will return null and we'll go to the default handling below. - * - * If the tree does match the NestedOptional shape, then we'll have recorded - * a sequence of declareProperty calls, and the final visitProperty call here - * will record that optional chain as a dependency (since we know it's about - * to be referenced via its lvalue which is non-null). - */ - if ( - lvalue !== null && - value.optional && - this.env.config.enableOptionalDependencies - ) { - const inner = this.extractOptionalProperty(context, value, lvalue); - if (inner !== null) { - context.visitProperty(inner.object, inner.property, inner.optional); - return; - } - } - - // Otherwise we treat everything after the optional as conditional - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value, null); - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - this.visitOptionalExpression(context, id, value, lvalue); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left, null); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right, null); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test, null); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent, null); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate, null); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.env.config.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property, false); - } else { - context.visitProperty(value.object, value.property, false); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value, lvalue); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue( - context, - terminal.id, - terminal.update, - null, - ); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test, null); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index eb77830561..8841ae9279 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9..d6331db4e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md index 56ca1f7722..839821b349 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -38,16 +38,24 @@ import { identity } from "shared-runtime"; * try-catch block, as that might throw */ function useFoo(maybeNullObject) { - const $ = _c(2); + const $ = _c(4); let y; - if ($[0] !== maybeNullObject.value.inner) { + if ($[0] !== maybeNullObject) { y = []; try { - y.push(identity(maybeNullObject.value.inner)); + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); } catch { y.push("null"); } - $[0] = maybeNullObject.value.inner; + $[0] = maybeNullObject; $[1] = y; } else { y = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac4149..b31a16da90 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -37,7 +37,7 @@ function component(a, b) { } const y = t0; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[2] !== a || $[3] !== y) { z = { a }; const x = function () { z.a = 2; @@ -45,7 +45,7 @@ function component(a, b) { x(); $[2] = a; - $[3] = y.b; + $[3] = y; $[4] = z; } else { z = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a..3f795b604e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bf..5e708b95c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d..fa8348c200 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73..5db4756ad3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e7..42caf4e39b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000..d9c2b59999 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000..57b7d48fac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md index 75c5d61d40..8bf7f5bc71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-call-chain-in-optional.expect.md @@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = { 1 | function useFoo(props: {value: {x: string; y: string} | null}) { 2 | const value = props.value; > 3 | return createArray(value?.x, value?.y)?.join(', '); - | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3) + | ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3) 4 | } 5 | 6 | function createArray(...args: Array): Array { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md index 8cbaeb3f89..396292103f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional import { Stringify } from "shared-runtime"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx index 2ede54db5f..ab3e00f9ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr-conditional-access-2.tsx @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional import {Stringify} from 'shared-runtime'; function Component({props}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md index f2fa20feb5..76f27fdb3f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; @@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional function Component(props) { const $ = _c(5); let t0; - if ($[0] !== props) { + if ($[0] !== props.bar) { t0 = function getLength() { return props.bar.length; }; - $[0] = props; + $[0] = props.bar; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js index 9bff3e5cdb..6e59fb947d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/functionexpr–conditional-access.js @@ -1,4 +1,4 @@ -// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false +// @enableTreatFunctionDepsAsConditional function Component(props) { function getLength() { return props.bar.length; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md index bed1c329f0..a578e4a41d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/iife-return-modified-later-phi.expect.md @@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let items; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a) { let t0; if (props.cond) { t0 = []; @@ -38,10 +38,11 @@ function Component(props) { items = t0; items?.push(props.a); - $[0] = props; - $[1] = items; + $[0] = props.cond; + $[1] = props.a; + $[2] = items; } else { - items = $[1]; + items = $[2]; } return items; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md index 31e2cadf9f..f415c20528 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-sequential-optional-chain-nonnull.expect.md @@ -33,11 +33,11 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let x; - if ($[0] !== a.b.c.d) { + if ($[0] !== a.b.c.d.e) { x = []; x.push(a?.b.c?.d.e); x.push(a.b?.c.d?.e); - $[0] = a.b.c.d; + $[0] = a.b.c.d.e; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md index 0acf33b2ed..92a24194a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-optional-chains.expect.md @@ -120,29 +120,29 @@ function useFoo(t0) { } const x = t1; let t2; - if ($[2] !== prop2?.inner) { + if ($[2] !== prop2?.inner.value) { t2 = identity(prop2?.inner.value)?.toString(); - $[2] = prop2?.inner; + $[2] = prop2?.inner.value; $[3] = t2; } else { t2 = $[3]; } const y = t2; let t3; - if ($[4] !== prop3 || $[5] !== prop4) { + if ($[4] !== prop3 || $[5] !== prop4?.inner) { t3 = prop3?.fn(prop4?.inner.value).toString(); $[4] = prop3; - $[5] = prop4; + $[5] = prop4?.inner; $[6] = t3; } else { t3 = $[6]; } const z = t3; let t4; - if ($[7] !== prop5 || $[8] !== prop6) { + if ($[7] !== prop5 || $[8] !== prop6?.inner) { t4 = prop5?.fn(prop6?.inner.value)?.toString(); $[7] = prop5; - $[8] = prop6; + $[8] = prop6?.inner; $[9] = t4; } else { t4 = $[9]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md index 8a20f9186b..b5534114c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-mutated-in-consequent-alternate-both-return.expect.md @@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeObject_Primitives } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.value) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const object = makeObject_Primitives(); @@ -45,10 +45,11 @@ function Component(props) { break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.cond; + $[1] = props.value; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md deleted file mode 100644 index 77ded20d93..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional-optional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props?.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md deleted file mode 100644 index 10c23085d8..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-member-expression-with-conditional.expect.md +++ /dev/null @@ -1,74 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import {ValidateMemoization} from 'shared-runtime'; -function Component(props) { - const data = useMemo(() => { - const x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - return x; - }, [props?.items, props.cond]); - return ( - - ); -} - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies -import { ValidateMemoization } from "shared-runtime"; -function Component(props) { - const $ = _c(9); - - props?.items; - let t0; - let x; - if ($[0] !== props?.items || $[1] !== props.cond) { - x = []; - x.push(props?.items); - if (props.cond) { - x.push(props.items); - } - $[0] = props?.items; - $[1] = props.cond; - $[2] = x; - } else { - x = $[2]; - } - t0 = x; - const data = t0; - - const t1 = props?.items; - let t2; - if ($[3] !== t1 || $[4] !== props.cond) { - t2 = [t1, props.cond]; - $[3] = t1; - $[4] = props.cond; - $[5] = t2; - } else { - t2 = $[5]; - } - let t3; - if ($[6] !== t2 || $[7] !== data) { - t3 = ; - $[6] = t2; - $[7] = data; - $[8] = t3; - } else { - t3 = $[8]; - } - return t3; -} - -``` - -### Eval output -(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md index 398161f0c6..266d87628c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/partial-early-return-within-reactive-scope.expect.md @@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -43,11 +43,11 @@ function Component(props) { break bb0; } else { let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[3] = t1; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } y = t1; if (props.b) { @@ -56,12 +56,14 @@ function Component(props) { } } } - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.b; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md index f17bcc92cb..16edbf2e23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push-consecutive-phis.expect.md @@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -59,7 +59,12 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ( + $[1] !== props.cond || + $[2] !== props.cond2 || + $[3] !== props.value || + $[4] !== props.value2 + ) { let y; if (props.cond) { if (props.cond2) { @@ -74,10 +79,13 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.cond2; + $[3] = props.value; + $[4] = props.value2; + $[5] = t1; } else { - t1 = $[2]; + t1 = $[5]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md index f58eed10fd..58e2c8f869 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -46,7 +46,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.value) { let y; if (props.cond) { y = [props.value]; @@ -57,10 +57,11 @@ function Component(props) { y.push(x); t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.value; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md index 70551c8e9d..641711e893 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/phi-type-inference-property-store.expect.md @@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @debug function Component(props) { - const $ = _c(3); + const $ = _c(4); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = {}; @@ -42,7 +42,7 @@ function Component(props) { } const x = t0; let t1; - if ($[1] !== props) { + if ($[1] !== props.cond || $[2] !== props.a) { let y; if (props.cond) { y = {}; @@ -53,10 +53,11 @@ function Component(props) { y.x = x; t1 = [x, y]; - $[1] = props; - $[2] = t1; + $[1] = props.cond; + $[2] = props.a; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md new file mode 100644 index 0000000000..8579b773e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; + +function Component({propA, propB}) { + return useCallback(() => { + if (propA) { + return { + value: propB.x.y, + }; + } + }, [propA, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{propA: 1, propB: {x: {y: []}}}], +}; + +``` + + +## Error + +``` + 3 | + 4 | function Component({propA, propB}) { +> 5 | return useCallback(() => { + | ^^^^^^^ +> 6 | if (propA) { + | ^^^^^^^^^^^^^^^^ +> 7 | return { + | ^^^^^^^^^^^^^^^^ +> 8 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^ +> 9 | }; + | ^^^^^^^^^^^^^^^^ +> 10 | } + | ^^^^^^^^^^^^^^^^ +> 11 | }, [propA, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11) + 12 | } + 13 | + 14 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md new file mode 100644 index 0000000000..e77e79fd98 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {identity, mutate} from 'shared-runtime'; + +function useHook(propA, propB) { + return useCallback(() => { + const x = {}; + if (identity(null) ?? propA.a) { + mutate(x); + return { + value: propB.x.y, + }; + } + }, [propA.a, propB.x.y]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useHook, + params: [{a: 1}, {x: {y: 3}}], +}; + +``` + + +## Error + +``` + 4 | + 5 | function useHook(propA, propB) { +> 6 | return useCallback(() => { + | ^^^^^^^ +> 7 | const x = {}; + | ^^^^^^^^^^^^^^^^^ +> 8 | if (identity(null) ?? propA.a) { + | ^^^^^^^^^^^^^^^^^ +> 9 | mutate(x); + | ^^^^^^^^^^^^^^^^^ +> 10 | return { + | ^^^^^^^^^^^^^^^^^ +> 11 | value: propB.x.y, + | ^^^^^^^^^^^^^^^^^ +> 12 | }; + | ^^^^^^^^^^^^^^^^^ +> 13 | } + | ^^^^^^^^^^^^^^^^^ +> 14 | }, [propA.a, propB.x.y]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + 15 | } + 16 | + 17 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.ts rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 940b3975c1..955d391f91 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,6 +44,8 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) + +CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md deleted file mode 100644 index a90492f7a1..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-conditional-access-own-scope.expect.md +++ /dev/null @@ -1,58 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; - -function Component({propA, propB}) { - return useCallback(() => { - if (propA) { - return { - value: propB.x.y, - }; - } - }, [propA, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{propA: 1, propB: {x: {y: []}}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; - -function Component(t0) { - const $ = _c(3); - const { propA, propB } = t0; - let t1; - if ($[0] !== propA || $[1] !== propB.x.y) { - t1 = () => { - if (propA) { - return { value: propB.x.y }; - } - }; - $[0] = propA; - $[1] = propB.x.y; - $[2] = t1; - } else { - t1 = $[2]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [{ propA: 1, propB: { x: { y: [] } } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md deleted file mode 100644 index d6c01643f5..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-infer-conditional-value-block.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {identity, mutate} from 'shared-runtime'; - -function useHook(propA, propB) { - return useCallback(() => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { - value: propB.x.y, - }; - } - }, [propA.a, propB.x.y]); -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{a: 1}, {x: {y: 3}}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { identity, mutate } from "shared-runtime"; - -function useHook(propA, propB) { - const $ = _c(3); - let t0; - if ($[0] !== propA.a || $[1] !== propB.x.y) { - t0 = () => { - const x = {}; - if (identity(null) ?? propA.a) { - mutate(x); - return { value: propB.x.y }; - } - }; - $[0] = propA.a; - $[1] = propB.x.y; - $[2] = t0; - } else { - t0 = $[2]; - } - return t0; -} - -export const FIXTURE_ENTRYPOINT = { - fn: useHook, - params: [{ a: 1 }, { x: { y: 3 } }], -}; - -``` - -### Eval output -(kind: ok) "[[ function params=0 ]]" \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md index 12a84b14f4..896a547fec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-dependencies-non-optional-properties-inside-optional-chain.expect.md @@ -15,9 +15,9 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.post.feedback.comments) { + if ($[0] !== props.post.feedback.comments?.edges) { t0 = props.post.feedback.comments?.edges?.map(render); - $[0] = props.post.feedback.comments; + $[0] = props.post.feedback.comments?.edges; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md index 5c6c680e05..39ce103cca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassigned-phi-in-returned-function-expression.expect.md @@ -23,7 +23,7 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.str) { + if ($[0] !== props) { t0 = () => { let str; if (arguments.length) { @@ -34,7 +34,7 @@ function Component(props) { global.log(str); }; - $[0] = props.str; + $[0] = props; $[1] = t0; } else { t0 = $[1]; 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 index 4d45d3f3c6..352552bf02 100644 --- 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 @@ -38,7 +38,7 @@ function Foo(t0) { const $ = _c(3); const { a, shouldReadA } = t0; let t1; - if ($[0] !== shouldReadA || $[1] !== a.b.c) { + if ($[0] !== shouldReadA || $[1] !== a) { t1 = ( { @@ -51,7 +51,7 @@ function Foo(t0) { /> ); $[0] = shouldReadA; - $[1] = a.b.c; + $[1] = a; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md index 9a95e7dc87..fa265ae1f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond.expect.md @@ -65,12 +65,12 @@ function useFoo(t0) { const $ = _c(2); const { screen } = t0; let t1; - if ($[0] !== screen?.title_text) { + if ($[0] !== screen) { t1 = screen?.title_text != null ? "(not null)" : identity({ title: screen.title_text }); - $[0] = screen?.title_text; + $[0] = screen; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md index c54d0828ec..37d347cd9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md @@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization, import { CONST_TRUE, setProperty } from "shared-runtime"; function useJoinCondDepsInUncondScopes(props) { - const $ = _c(4); + const $ = _c(2); let t0; if ($[0] !== props.a.b) { const y = {}; - let x; - if ($[2] !== props) { - x = {}; - if (CONST_TRUE) { - setProperty(x, props.a.b); - } - $[2] = props; - $[3] = x; - } else { - x = $[3]; + const x = {}; + if (CONST_TRUE) { + setProperty(x, props.a.b); } setProperty(y, props.a.b); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md index 09806d8b4b..9186ec84d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md @@ -34,19 +34,20 @@ import { identity } from "shared-runtime"; // and promote it to an unconditional dependency. function usePromoteUnconditionalAccessToDependency(props, other) { - const $ = _c(3); + const $ = _c(4); let x; - if ($[0] !== props.a || $[1] !== other) { + if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) { x = {}; x.a = props.a.a.a; if (identity(other)) { x.c = props.a.b.c; } - $[0] = props.a; - $[1] = other; - $[2] = x; + $[0] = props.a.a.a; + $[1] = props.a.b; + $[2] = other; + $[3] = x; } else { - x = $[2]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md index 6af0cf0af7..c39b85e5ba 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-cascading-eliminated-phis.expect.md @@ -36,10 +36,16 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(7); let x = 0; let values; - if ($[0] !== props || $[1] !== x) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d || + $[4] !== x + ) { values = []; const y = props.a || props.b; values.push(y); @@ -53,13 +59,16 @@ function Component(props) { } values.push(x); - $[0] = props; - $[1] = x; - $[2] = values; - $[3] = x; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = x; + $[5] = values; + $[6] = x; } else { - values = $[2]; - x = $[3]; + values = $[5]; + x = $[6]; } return values; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md index a10ad5fae4..dd61d1fee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-leave-case.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function Component(props) { - const $ = _c(2); + const $ = _c(3); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1) { const x = []; let y; if (props.p0) { @@ -55,10 +55,11 @@ function Component(props) { {y} ); - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = t0; } else { - t0 = $[1]; + t0 = $[2]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md index 3e7fd4bf5f..c6c7489a4e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? (([x] = [[]]), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md index 9b3aad524c..693b94d886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-destruction.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? (([x] = [[]]), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md index de9466c4da..283e55630b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : null; mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md index e199863257..97cfa052af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-ternary.expect.md @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(5); let x; if ($[0] !== props.bar) { x = []; @@ -36,12 +36,13 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo) { props.cond ? ((x = []), x.push(props.foo)) : null; - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = x; } else { - x = $[3]; + x = $[4]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md index 16981f69cd..1c4b48cb7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary-with-mutation.expect.md @@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; import { arrayPush } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); arrayPush(x, 4); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md index 99b50ac231..5571c3cfe5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-ternary.expect.md @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function useFoo(props) { - const $ = _c(4); + const $ = _c(6); let x; if ($[0] !== props.bar) { x = []; @@ -38,12 +38,14 @@ function useFoo(props) { } else { x = $[1]; } - if ($[2] !== props) { + if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) { props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar)); - $[2] = props; - $[3] = x; + $[2] = props.cond; + $[3] = props.foo; + $[4] = props.bar; + $[5] = x; } else { - x = $[3]; + x = $[5]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md index f4689e5795..9f1e21d7c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-unconditional-with-mutation.expect.md @@ -39,9 +39,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -53,10 +53,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md index ed1056c47c..81cc777522 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { ({ x } = { x: [] }); x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md index 26cd73a82b..f48cec2c23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ssa-renaming-with-mutation.expect.md @@ -35,9 +35,9 @@ import { c as _c } from "react/compiler-runtime"; import { mutate } from "shared-runtime"; function useFoo(props) { - const $ = _c(2); + const $ = _c(4); let x; - if ($[0] !== props) { + if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) { x = []; x.push(props.bar); if (props.cond) { @@ -46,10 +46,12 @@ function useFoo(props) { } mutate(x); - $[0] = props; - $[1] = x; + $[0] = props.bar; + $[1] = props.cond; + $[2] = props.foo; + $[3] = x; } else { - x = $[1]; + x = $[3]; } return x; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md index 915218fcfa..0a5e7103c6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch-non-final-default.expect.md @@ -33,10 +33,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(7); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2) { const x = []; bb0: switch (props.p0) { case 1: { @@ -45,11 +45,11 @@ function Component(props) { case true: { x.push(props.p2); let t1; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { t1 = []; - $[3] = t1; + $[4] = t1; } else { - t1 = $[3]; + t1 = $[4]; } y = t1; } @@ -62,23 +62,24 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = y; + $[3] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[2]; + t0 = $[3]; } const child = t0; y.push(props.p4); let t1; - if ($[4] !== y || $[5] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[4] = y; - $[5] = child; - $[6] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[6]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md index 0c5aea9c7d..b83c1fcb7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/switch.expect.md @@ -28,10 +28,10 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(8); let y; let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) { const x = []; switch (props.p0) { case true: { @@ -44,23 +44,25 @@ function Component(props) { } t0 = ; - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.p0; + $[1] = props.p2; + $[2] = props.p3; + $[3] = y; + $[4] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[3]; + t0 = $[4]; } const child = t0; y.push(props.p4); let t1; - if ($[3] !== y || $[4] !== child) { + if ($[5] !== y || $[6] !== child) { t1 = {child}; - $[3] = y; - $[4] = child; - $[5] = t1; + $[5] = y; + $[6] = child; + $[7] = t1; } else { - t1 = $[5]; + t1 = $[7]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md index 856d132640..cab72226d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime"; const { shallowCopy, throwErrorWithMessage } = require("shared-runtime"); function Component(props) { - const $ = _c(3); + const $ = _c(5); let x; - if ($[0] !== props.a) { + if ($[0] !== props) { x = []; try { let t0; @@ -42,9 +42,17 @@ function Component(props) { } x.push(t0); } catch { - x.push(shallowCopy({ a: props.a })); + let t0; + if ($[3] !== props.a) { + t0 = shallowCopy({ a: props.a }); + $[3] = props.a; + $[4] = t0; + } else { + t0 = $[4]; + } + x.push(t0); } - $[0] = props.a; + $[0] = props; $[1] = x; } else { x = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md index f2e46a6aff..db8877f061 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-function-expression-returns-caught-value.expect.md @@ -31,7 +31,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { t0 = () => { try { throwInput([props.value]); @@ -40,7 +40,7 @@ function Component(props) { return e; } }; - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md index 83f97ff6cb..b760716a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/try-catch-within-object-method-returns-caught-value.expect.md @@ -33,7 +33,7 @@ import { throwInput } from "shared-runtime"; function Component(props) { const $ = _c(2); let t0; - if ($[0] !== props.value) { + if ($[0] !== props) { const object = { foo() { try { @@ -46,7 +46,7 @@ function Component(props) { }; t0 = object.foo(); - $[0] = props.value; + $[0] = props; $[1] = t0; } else { t0 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md index 05e465000d..03725703f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md @@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime"; import { useMemo } from "react"; function Component(props) { - const $ = _c(3); + const $ = _c(6); let t0; bb0: { let y; - if ($[0] !== props) { + if ( + $[0] !== props.cond || + $[1] !== props.a || + $[2] !== props.cond2 || + $[3] !== props.b + ) { y = []; if (props.cond) { y.push(props.a); @@ -48,12 +53,15 @@ function Component(props) { } y.push(props.b); - $[0] = props; - $[1] = y; - $[2] = t0; + $[0] = props.cond; + $[1] = props.a; + $[2] = props.cond2; + $[3] = props.b; + $[4] = y; + $[5] = t0; } else { - y = $[1]; - t0 = $[2]; + y = $[4]; + t0 = $[5]; } t0 = y; } From 3c9bf70e6d47c3fea8fb43bd0b916d654b49f941 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 58/63] [compiler] Collect temporaries and optional chains from inner functions Recursively collect identifier / property loads and optional chains from inner functions. This PR is in preparation for #31200 Previously, we only did this in `collectHoistablePropertyLoads` to understand hoistable property loads from inner functions. 1. collectTemporariesSidemap 2. collectOptionalChainSidemap 3. collectHoistablePropertyLoads - ^ this recursively calls `collectTemporariesSidemap`, `collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on inner functions 4. collectDependencies Now, we have 1. collectTemporariesSidemap - recursively record identifiers in inner functions. Note that we track all temporaries in the same map as `IdentifierIds` are currently unique across functions 2. collectOptionalChainSidemap - recursively records optional chain sidemaps in inner functions 3. collectHoistablePropertyLoads - (unchanged, except to remove recursive collection of temporaries) 4. collectDependencies - unchanged: to be modified to recursively collect dependencies in next PR ' --- .../src/HIR/CollectHoistablePropertyLoads.ts | 9 -- .../HIR/CollectOptionalChainDependencies.ts | 66 +++++++--- .../src/HIR/PropagateScopeDependenciesHIR.ts | 118 ++++++++++++++---- .../src/HIR/visitors.ts | 8 ++ 4 files changed, 151 insertions(+), 50 deletions(-) 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 456425aeca..d3c919a6d8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -8,7 +8,6 @@ import { Set_union, getOrInsertDefault, } from '../Utils/utils'; -import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; import { BasicBlock, BlockId, @@ -22,7 +21,6 @@ import { ReactiveScopeDependency, ScopeId, } from './HIR'; -import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR'; const DEBUG_PRINT = false; @@ -373,17 +371,10 @@ function collectNonNullsInBlocks( !fn.env.config.enableTreatFunctionDepsAsConditional ) { const innerFn = instr.value.loweredFunc; - const innerTemporaries = collectTemporariesSidemap( - innerFn.func, - new Set(), - ); - const innerOptionals = collectOptionalChainSidemap(innerFn.func); const innerHoistableMap = collectHoistablePropertyLoadsImpl( innerFn.func, { ...context, - temporaries: innerTemporaries, // TODO: remove in later PR - hoistableFromOptionals: innerOptionals.hoistableObjects, // TODO: remove in later PR nestedFnImmutableContext: context.nestedFnImmutableContext ?? new Set( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts index 4532947842..0167c996b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -1,4 +1,5 @@ import {CompilerError} from '..'; +import {getOrInsertDefault} from '../Utils/utils'; import {assertNonNull} from './CollectHoistablePropertyLoads'; import { BlockId, @@ -22,25 +23,14 @@ export function collectOptionalChainSidemap( fn: HIRFunction, ): OptionalChainSidemap { const context: OptionalTraversalContext = { + currFn: fn, blocks: fn.body.blocks, seenOptionals: new Set(), - processedInstrsInOptional: new Set(), + processedInstrsInOptional: new Map(), temporariesReadInOptional: new Map(), hoistableObjects: new Map(), }; - for (const [_, block] of fn.body.blocks) { - if ( - block.terminal.kind === 'optional' && - !context.seenOptionals.has(block.id) - ) { - traverseOptionalBlock( - block as TBasicBlock, - context, - null, - ); - } - } - + traverseFunction(fn, context); return { temporariesReadInOptional: context.temporariesReadInOptional, processedInstrsInOptional: context.processedInstrsInOptional, @@ -96,8 +86,13 @@ export type OptionalChainSidemap = { * bb5: * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! * ``` + * + * Also note that InstructionIds are not unique across inner functions. */ - processedInstrsInOptional: ReadonlySet; + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >; /** * Records optional chains for which we can safely evaluate non-optional * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at @@ -115,16 +110,46 @@ export type OptionalChainSidemap = { }; type OptionalTraversalContext = { + currFn: HIRFunction; blocks: ReadonlyMap; // Track optional blocks to avoid outer calls into nested optionals seenOptionals: Set; - processedInstrsInOptional: Set; + processedInstrsInOptional: Map>; temporariesReadInOptional: Map; hoistableObjects: Map; }; +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} /** * Match the consequent and alternate blocks of an optional. * @returns propertyload computed by the consequent block, or null if the @@ -369,10 +394,13 @@ function traverseOptionalBlock( }, ], }; - context.processedInstrsInOptional.add( - matchConsequentResult.storeLocalInstrId, + const processedInstrsInOptionalByFn = getOrInsertDefault( + context.processedInstrsInOptional, + context.currFn, + new Set(), ); - context.processedInstrsInOptional.add(test.id); + processedInstrsInOptionalByFn.add(matchConsequentResult.storeLocalInstrId); + processedInstrsInOptionalByFn.add(test.id); context.temporariesReadInOptional.set( matchConsequentResult.consequentId, load, 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 0178aea6e4..8f4abdf6da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -176,8 +176,10 @@ function findTemporariesUsedOutsideDeclaringScope( * $2 = LoadLocal 'foo' * $3 = CallExpression $2($1) * ``` - * Only map LoadLocal and PropertyLoad lvalues to their source if we know that - * reordering the read (from the time-of-load to time-of-use) is valid. + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. * * If a LoadLocal or PropertyLoad instruction is within the reactive scope range * (a proxy for mutable range) of the load source, later instructions may @@ -215,7 +217,29 @@ export function collectTemporariesSidemap( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, ): ReadonlyMap { - const temporaries = new Map(); + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { for (const [_, block] of fn.body.blocks) { for (const instr of block.instructions) { const {value, lvalue} = instr; @@ -224,27 +248,51 @@ export function collectTemporariesSidemap( ); if (value.kind === 'PropertyLoad' && !usedOutside) { - const property = getProperty( - value.object, - value.property, - false, - temporaries, - ); - temporaries.set(lvalue.identifier.id, property); + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } } else if ( value.kind === 'LoadLocal' && lvalue.identifier.name == null && value.place.identifier.name !== null && !usedOutside ) { - temporaries.set(lvalue.identifier.id, { - identifier: value.place.identifier, - path: [], - }); + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); } } } - return temporaries; } function getProperty( @@ -310,6 +358,12 @@ class Context { #temporaries: ReadonlyMap; #temporariesUsedOutsideScope: ReadonlySet; + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + constructor( temporariesUsedOutsideScope: ReadonlySet, temporaries: ReadonlyMap, @@ -360,12 +414,23 @@ class Context { } /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). */ declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; if (!this.#declarations.has(identifier.declarationId)) { this.#declarations.set(identifier.declarationId, decl); } @@ -575,7 +640,10 @@ function collectDependencies( fn: HIRFunction, usedOutsideDeclaringScope: ReadonlySet, temporaries: ReadonlyMap, - processedInstrsInOptional: ReadonlySet, + processedInstrsInOptional: ReadonlyMap< + HIRFunction, + ReadonlySet + >, ): Map> { const context = new Context(usedOutsideDeclaringScope, temporaries); @@ -595,6 +663,12 @@ function collectDependencies( const scopeTraversal = new ScopeBlockTraversal(); + const shouldSkipInstructionDependencies = ( + fn: HIRFunction, + id: InstructionId, + ): boolean => { + return processedInstrsInOptional.get(fn)?.has(id) ?? false; + }; for (const [blockId, block] of fn.body.blocks) { scopeTraversal.recordScopes(block); const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); @@ -614,12 +688,12 @@ function collectDependencies( } } for (const instr of block.instructions) { - if (!processedInstrsInOptional.has(instr.id)) { + if (!shouldSkipInstructionDependencies(fn, instr.id)) { handleInstruction(instr, context); } } - if (!processedInstrsInOptional.has(block.terminal.id)) { + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { for (const place of eachTerminalOperand(block.terminal)) { context.visitOperand(place); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 217bc3132b..c9ee803bfa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal { } } + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ isScopeActive(scopeId: ScopeId): boolean { return this.#activeScopes.indexOf(scopeId) !== -1; } + + /** + * The current, innermost active scope. + */ get currentScope(): ScopeId | null { return this.#activeScopes.at(-1) ?? null; } From a19e4bb85c7322198be35c04b5f0cb1451565e24 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 59/63] [compiler] Stop using function `dependencies` in propagateScopeDeps Recursively visit inner function instructions to extract dependencies instead of using `LoweredFunction.dependencies` directly. This is currently gated by enableFunctionDependencyRewrite, which needs to be removed before we delete `LoweredFunction.dependencies` altogether (#31204). Some nice side effects - optional-chaining deps for inner functions - full DCE and outlining for inner functions (see #31202) - fewer extraneous instructions (see #31204) - --- .../src/HIR/Environment.ts | 2 + .../src/HIR/PropagateScopeDependenciesHIR.ts | 70 ++++++++++------ .../capturing-func-mutate-2.expect.md | 21 ++--- ...jsx-outlining-child-stored-in-id.expect.md | 6 +- ...ures-reassigned-context-property.expect.md | 53 ++++++++++++ ...k-captures-reassigned-context-property.tsx | 32 ++++++++ ...less-specific-conditional-access.expect.md | 2 - ...ures-reassigned-context-property.expect.md | 81 ------------------- ...k-captures-reassigned-context-property.tsx | 21 ----- ...back-captures-reassigned-context.expect.md | 16 ++-- ...llback-extended-contextvar-scope.expect.md | 28 +++---- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../compiler/react-namespace.expect.md | 26 +++--- ...unction-uncond-optionals-hoisted.expect.md | 4 +- .../ref-parameter-mutate-in-effect.expect.md | 28 ++++--- 15 files changed, 195 insertions(+), 199 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index ce38e91af4..fab2111735 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), + enableFunctionDependencyRewrite: z.boolean().default(true), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime 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 8f4abdf6da..bd938db03e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -669,35 +669,55 @@ function collectDependencies( ): boolean => { return processedInstrsInOptional.get(fn)?.has(id) ?? false; }; - for (const [blockId, block] of fn.body.blocks) { - scopeTraversal.recordScopes(block); - const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); - if (scopeBlockInfo?.kind === 'begin') { - context.enterScope(scopeBlockInfo.scope); - } else if (scopeBlockInfo?.kind === 'end') { - context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned); - } - // Record referenced optional chains in phis - for (const phi of block.phis) { - for (const operand of phi.operands) { - const maybeOptionalChain = temporaries.get(operand[1].identifier.id); - if (maybeOptionalChain) { - context.visitDependency(maybeOptionalChain); + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!shouldSkipInstructionDependencies(fn, instr.id)) { + handleInstruction(instr, context); + } + } + + if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); } } } - for (const instr of block.instructions) { - if (!shouldSkipInstructionDependencies(fn, instr.id)) { - handleInstruction(instr, context); - } - } + }; - if (!shouldSkipInstructionDependencies(fn, block.terminal.id)) { - for (const place of eachTerminalOperand(block.terminal)) { - context.visitOperand(place); - } - } - } + handleFunction(fn); return context.deps; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index b31a16da90..c071d5d20e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index fd7ca41bcf..86e9adaabc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -53,7 +53,7 @@ function Component(arr) { const $ = _c(3); const x = useX(); let t0; - if ($[0] !== arr || $[1] !== x) { + if ($[0] !== x || $[1] !== arr) { t0 = arr.map((i) => { arr.map((i_0, id) => { const T0 = _temp; @@ -63,8 +63,8 @@ function Component(arr) { return jsx; }); }); - $[0] = arr; - $[1] = x; + $[0] = x; + $[1] = arr; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md new file mode 100644 index 0000000000..ae44f27912 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; + +``` + + +## Error + +``` + 22 | } + 23 | +> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]); + | ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24) + 25 | + 26 | return ; + 27 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx new file mode 100644 index 0000000000..8447e3960d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.todo-useCallback-captures-reassigned-context-property.tsx @@ -0,0 +1,32 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useCallback} from 'react'; +import {Stringify} from 'shared-runtime'; + +/** + * TODO: we're currently bailing out because `contextVar` is a context variable + * and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad + * sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted + * `LoadContext` and `PropertyLoad` instructions into the outer function, which + * we took as eligible dependencies. + * + * One solution is to simply record `LoadContext` identifiers into the + * temporaries sidemap when the instruction occurs *after* the context + * variable's mutable range. + */ +function Foo(props) { + let contextVar; + if (props.cond) { + contextVar = {val: 2}; + } else { + contextVar = {}; + } + + const cb = useCallback(() => [contextVar.val], [contextVar.val]); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{cond: true}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index 955d391f91..940b3975c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -44,8 +44,6 @@ function Component({propA, propB}) { | ^^^^^^^^^^^^^^^^^ > 14 | }, [propA?.a, propB.x.y]); | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) - -CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14) 15 | } 16 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md deleted file mode 100644 index db69bc2821..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.expect.md +++ /dev/null @@ -1,81 +0,0 @@ - -## Input - -```javascript -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees -import { useCallback } from "react"; -import { Stringify } from "shared-runtime"; - -function Foo(props) { - const $ = _c(6); - let contextVar; - if ($[0] !== props.cond) { - if (props.cond) { - contextVar = { val: 2 }; - } else { - contextVar = {}; - } - $[0] = props.cond; - $[1] = contextVar; - } else { - contextVar = $[1]; - } - - const t0 = contextVar; - let t1; - if ($[2] !== t0.val) { - t1 = () => [contextVar.val]; - $[2] = t0.val; - $[3] = t1; - } else { - t1 = $[3]; - } - contextVar; - const cb = t1; - let t2; - if ($[4] !== cb) { - t2 = ; - $[4] = cb; - $[5] = t2; - } else { - t2 = $[5]; - } - return t2; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ cond: true }], -}; - -``` - -### Eval output -(kind: ok)
{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx deleted file mode 100644 index cb6f65a9f4..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context-property.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @validatePreserveExistingMemoizationGuarantees -import {useCallback} from 'react'; -import {Stringify} from 'shared-runtime'; - -function Foo(props) { - let contextVar; - if (props.cond) { - contextVar = {val: 2}; - } else { - contextVar = {}; - } - - const cb = useCallback(() => [contextVar.val], [contextVar.val]); - - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{cond: true}], -}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md index b66661fbca..41994e1e56 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-captures-reassigned-context.expect.md @@ -45,18 +45,16 @@ function Foo(props) { } else { x = $[1]; } - - const t0 = x; - let t1; - if ($[2] !== t0) { - t1 = () => [x]; - $[2] = t0; - $[3] = t1; + let t0; + if ($[2] !== x) { + t0 = () => [x]; + $[2] = x; + $[3] = t0; } else { - t1 = $[3]; + t0 = $[3]; } x; - const cb = t1; + const cb = t0; return cb; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md index b141c27614..96cec0cd26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-extended-contextvar-scope.expect.md @@ -70,28 +70,26 @@ function useBar(t0, cond) { if (cond) { x = b; } - - const t2 = x; - let t3; - if ($[1] !== a || $[2] !== t2) { - t3 = () => [a, x]; - $[1] = a; - $[2] = t2; - $[3] = t3; + let t2; + if ($[1] !== x || $[2] !== a) { + t2 = () => [a, x]; + $[1] = x; + $[2] = a; + $[3] = t2; } else { - t3 = $[3]; + t2 = $[3]; } x; - const cb = t3; - let t4; + const cb = t2; + let t3; if ($[4] !== cb) { - t4 = ; + t3 = ; $[4] = cb; - $[5] = t4; + $[5] = t3; } else { - t4 = $[5]; + t3 = $[5]; } - return t4; + return t3; } export const FIXTURE_ENTRYPOINT = { 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 index 02e60eff91..ed56ff0681 100644 --- 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 @@ -34,9 +34,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651b..cab231da32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime"; const FooContext = React.createContext({ current: null }); function Component(props) { - const $ = _c(5); + const $ = _c(7); React.useContext(FooContext); const ref = React.useRef(); const [x, setX] = React.useState(false); let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { setX(true); ref.current = true; }; - $[0] = t0; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; + t0 = $[1]; } const onClick = t0; let t1; - if ($[1] !== props.children) { + if ($[2] !== props.children) { t1 = React.cloneElement(props.children); - $[1] = props.children; - $[2] = t1; + $[2] = props.children; + $[3] = t1; } else { - t1 = $[2]; + t1 = $[3]; } let t2; - if ($[3] !== t1) { + if ($[4] !== onClick || $[5] !== t1) { t2 =
{t1}
; - $[3] = t1; - $[4] = t2; + $[4] = onClick; + $[5] = t1; + $[6] = t2; } else { - t2 = $[4]; + t2 = $[6]; } return t2; } 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 index 157e2de81a..bb99a5d90f 100644 --- 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 @@ -31,9 +31,9 @@ function useFoo(t0) { const $ = _c(2); const { a } = t0; let t1; - if ($[0] !== a.b) { + if ($[0] !== a.b?.c.d?.e) { t1 = a.b?.c.d?.e} shouldInvokeFns={true} />; - $[0] = a.b; + $[0] = a.b?.c.d?.e; $[1] = t1; } else { t1 = $[1]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md index 8b5a2eb1a0..95c6a403de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ref-parameter-mutate-in-effect.expect.md @@ -26,28 +26,32 @@ import { c as _c } from "react/compiler-runtime"; import { useEffect } from "react"; function Foo(props, ref) { - const $ = _c(4); + const $ = _c(5); let t0; - let t1; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + if ($[0] !== ref) { t0 = () => { ref.current = 2; }; - t1 = []; - $[0] = t0; - $[1] = t1; + $[0] = ref; + $[1] = t0; } else { - t0 = $[0]; - t1 = $[1]; + t0 = $[1]; + } + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[2] = t1; + } else { + t1 = $[2]; } useEffect(t0, t1); let t2; - if ($[2] !== props.bar) { + if ($[3] !== props.bar) { t2 =
{props.bar}
; - $[2] = props.bar; - $[3] = t2; + $[3] = props.bar; + $[4] = t2; } else { - t2 = $[3]; + t2 = $[4]; } return t2; } From ba32a1621f76c270495ae690195c6a0bae0d8673 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 60/63] [compiler] Lower JSXMemberExpression with LoadLocal `JSXMemberExpression` is currently the only instruction (that I know of) that directly references identifier lvalues without a corresponding `LoadLocal`. This has some side effects: - deadcode elimination and constant propagation now reach JSXMemberExpressions - we can delete `LoweredFunction.dependencies` without dangling references (previously, the only reference to JSXMemberExpression objects in HIR was in function dependencies) - JSXMemberExpression now is consistent with all other instructions (e.g. has a rvalue-producing LoadLocal) ' --- .../src/HIR/BuildHIR.ts | 8 +- .../invalid-jsx-lowercase-localvar.expect.md | 75 +++++++++++++++++++ .../invalid-jsx-lowercase-localvar.jsx | 29 +++++++ ...local-memberexpr-tag-conditional.expect.md | 3 +- .../jsx-local-memberexpr-tag.expect.md | 3 +- ...se-localvar-memberexpr-in-lambda.expect.md | 59 +++++++++++++++ ...owercase-localvar-memberexpr-in-lambda.jsx | 12 +++ ...sx-lowercase-localvar-memberexpr.expect.md | 45 +++++++++++ .../jsx-lowercase-localvar-memberexpr.jsx | 10 +++ .../jsx-lowercase-memberexpr.expect.md | 44 +++++++++++ .../compiler/jsx-lowercase-memberexpr.jsx | 9 +++ .../jsx-memberexpr-tag-in-lambda.expect.md | 3 +- .../packages/snap/src/SproutTodoFilter.ts | 3 + .../snap/src/sprout/shared-runtime.ts | 3 + 14 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 9494436d1f..ecc22365dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md new file mode 100644 index 0000000000..925346225c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.expect.md @@ -0,0 +1,75 @@ + +## Input + +```javascript +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Throw } from "shared-runtime"; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx new file mode 100644 index 0000000000..1e62eb0117 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-lowercase-localvar.jsx @@ -0,0 +1,29 @@ +import {Throw} from 'shared-runtime'; + +/** + * Note: this is disabled in the evaluator due to different devmode errors. + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag'] + * + * Forget: + * (kind: ok) + * logs: [ + * 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag', + * 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag', + * ] + */ +function useFoo() { + const invalidTag = Throw; + /** + * Need to be careful to not parse `invalidTag` as a localVar (i.e. render + * Throw). Note that the jsx transform turns this into a string tag: + * `jsx("invalidTag"... + */ + return ; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md index 0cb821459c..f13d3a0598 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag-conditional.expect.md @@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime"; function useFoo(t0) { const $ = _c(1); const { cond } = t0; - const MyLocal = SharedRuntime; if (cond) { let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t1 = ; + t1 = ; $[0] = t1; } else { t1 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md index ab11ddedb8..f24e7a754d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-memberexpr-tag.expect.md @@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = ; + t0 = ; $[0] = t0; } else { t0 = $[0]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md new file mode 100644 index 0000000000..2482347939 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +import { invoke } from "shared-runtime"; +function useComponentFactory(t0) { + const $ = _c(4); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = () => ( + hello world {name} + ); + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + const cb = t1; + let t2; + if ($[2] !== cb) { + t2 = invoke(cb); + $[2] = cb; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx new file mode 100644 index 0000000000..534490d5d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr-in-lambda.jsx @@ -0,0 +1,12 @@ +import * as SharedRuntime from 'shared-runtime'; +import {invoke} from 'shared-runtime'; +function useComponentFactory({name}) { + const localVar = SharedRuntime; + const cb = () => hello world {name}; + return invoke(cb); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useComponentFactory, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md new file mode 100644 index 0000000000..5778bf599f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.expect.md @@ -0,0 +1,45 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx new file mode 100644 index 0000000000..d55037fca0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-localvar-memberexpr.jsx @@ -0,0 +1,10 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + const localVar = SharedRuntime; + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md new file mode 100644 index 0000000000..f5f7b3727e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import * as SharedRuntime from "shared-runtime"; +function Component(t0) { + const $ = _c(2); + const { name } = t0; + let t1; + if ($[0] !== name) { + t1 = hello world {name}; + $[0] = name; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ name: "sathya" }], +}; + +``` + +### Eval output +(kind: ok)
{"children":["hello world ","sathya"]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx new file mode 100644 index 0000000000..992cbecebe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-lowercase-memberexpr.jsx @@ -0,0 +1,9 @@ +import * as SharedRuntime from 'shared-runtime'; +function Component({name}) { + return hello world {name}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{name: 'sathya'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 363f82d12c..22fa3b2e2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); - const MyLocal = SharedRuntime; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; + const callback = () => ; t0 = callback(); $[0] = t0; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 351f242e40..cc50fa3bd2 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -475,6 +475,9 @@ const skipFilter = new Set([ 'rules-of-hooks/rules-of-hooks-93dc5d5e538a', 'rules-of-hooks/rules-of-hooks-69521d94fa03', + // false positives + 'invalid-jsx-lowercase-localvar', + // bugs 'fbt/bug-fbt-plural-multiple-function-calls', 'fbt/bug-fbt-plural-multiple-mixed-call-tag', diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index 0f3e09b12e..e6e82d6b77 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -252,6 +252,9 @@ export function Stringify(props: any): React.ReactElement { toJSON(props, props?.shouldInvokeFns), ); } +export function Throw() { + throw new Error(); +} export function ValidateMemoization({ inputs, From bb3706c3ae6704286323ad7cbc9599406b1fceeb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 61/63] [compiler][be] Patch test fixtures for evaluator Add more `FIXTURE_ENTRYPOINT`s ' --- ...uring-func-alias-captured-mutate.expect.md | 46 +++++++++--- .../capturing-func-alias-captured-mutate.js | 20 ++++-- .../compiler/capturing-func-mutate.expect.md | 59 ++++++++++----- .../compiler/capturing-func-mutate.js | 21 ++++-- .../capturing-func-no-mutate.expect.md | 72 +++++++++++++++++++ .../compiler/capturing-func-no-mutate.js | 21 ++++++ .../packages/snap/src/SproutTodoFilter.ts | 2 - 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c96..732b77864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,39 +2,55 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; if ($[0] !== foo || $[1] !== bar) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); $[0] = foo; $[1] = bar; $[2] = y; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66..b88ad56718 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da7..fcde7d675c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084..2ec7bcbe86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000..aa32b3260e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000..8fe3bb3db5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index cc50fa3bd2..03f1b4c6e1 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -34,7 +34,6 @@ const skipFilter = new Set([ 'capturing-arrow-function-1', 'capturing-func-mutate-3', 'capturing-func-mutate-nested', - 'capturing-func-mutate', 'capturing-function-1', 'capturing-function-alias-computed-load', 'capturing-function-decl', @@ -236,7 +235,6 @@ const skipFilter = new Set([ 'capturing-fun-alias-captured-mutate-2', 'capturing-fun-alias-captured-mutate-arr-2', 'capturing-func-alias-captured-mutate-arr', - 'capturing-func-alias-captured-mutate', 'capturing-func-alias-computed-mutate', 'capturing-func-alias-mutate', 'capturing-func-alias-receiver-computed-mutate', From 52a7bfaa332181e6c02bcff88cb33d0146eec122 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 62/63] [compiler][be] Clean up nested function context in DCE Now that we rely on function context exclusively, let's clean up `HIRFunction.context` after DCE. This PR is in preparation of #31204, which would otherwise have unnecessary declarations (of context values that become entirely DCE'd) ' --- .../src/Optimization/DeadCodeElimination.ts | 8 ++++ .../compiler/arrow-expr-directive.expect.md | 5 ++- .../compiler/capture-param-mutate.expect.md | 9 ++-- .../function-expr-directive.expect.md | 5 ++- .../compiler/merge-scopes-callback.expect.md | 5 ++- ...reactive-scope-with-early-return.expect.md | 42 ++++++++----------- ...react-hooks-based-on-import-name.expect.md | 5 ++- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 885ec2b3ab..0202d3ecf0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103..93eb2bd28a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c..9e4709616d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md index 3980434bde..8c4aa612e8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/function-expr-directive.expect.md @@ -34,7 +34,7 @@ function Component() { t0 = function update() { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -51,6 +51,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md index edf748de5c..0ff9773f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-scopes-callback.expect.md @@ -32,7 +32,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -61,6 +61,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index 0c1bf1cd70..506e4ca713 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -39,7 +39,7 @@ function Component() { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions function Component() { - const $ = _c(8); + const $ = _c(7); const items = useItems(); let t0; let t1; @@ -47,35 +47,25 @@ function Component() { if ($[0] !== items) { t2 = Symbol.for("react.early_return_sentinel"); bb0: { - let t3; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { - t3 = (t4) => { - const [item] = t4; - return item.name != null; - }; - $[4] = t3; - } else { - t3 = $[4]; - } - t0 = items.filter(t3); + t0 = items.filter(_temp); const filteredItems = t0; if (filteredItems.length === 0) { - let t4; - if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t4 = ( + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = (
); - $[5] = t4; + $[4] = t3; } else { - t4 = $[5]; + t3 = $[4]; } - t2 = t4; + t2 = t3; break bb0; } - t1 = filteredItems.map(_temp); + t1 = filteredItems.map(_temp2); } $[0] = items; $[1] = t1; @@ -90,19 +80,23 @@ function Component() { return t2; } let t3; - if ($[6] !== t1) { + if ($[5] !== t1) { t3 = <>{t1}; - $[6] = t1; - $[7] = t3; + $[5] = t1; + $[6] = t3; } else { - t3 = $[7]; + t3 = $[6]; } return t3; } -function _temp(t0) { +function _temp2(t0) { const [item_0] = t0; return ; } +function _temp(t0) { + const [item] = t0; + return item.name != null; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md index dc3081321e..496d61df9d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/resolve-react-hooks-based-on-import-name.expect.md @@ -38,7 +38,7 @@ function Component() { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { - setState((s) => s + 1); + setState(_temp); }; $[0] = t0; } else { @@ -67,6 +67,9 @@ function Component() { } return t2; } +function _temp(s) { + return s + 1; +} export const FIXTURE_ENTRYPOINT = { fn: Component, From 77fefaa6282e840f654e11ae1115e08924d21ffc Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 5 Nov 2024 14:03:36 -0500 Subject: [PATCH 63/63] [compiler] Delete LoweredFunction.dependencies and hoisted instructions LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' --- .../src/HIR/BuildHIR.ts | 152 ++---------------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +---- .../src/HIR/Environment.ts | 1 - .../src/HIR/HIR.ts | 1 - .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 62 ++----- .../Inference/InferMutableContextVariables.ts | 16 -- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../src/SSA/EliminateRedundantPhi.ts | 19 +++ .../src/SSA/EnterSSA.ts | 3 - ...access-in-unused-callback-nested.expect.md | 40 +++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...p-with-context-variable-iterator.expect.md | 2 +- ...on-with-shadowed-local-same-name.expect.md | 2 +- .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 ++-- .../use-operator-conditional.expect.md | 1 - 38 files changed, 130 insertions(+), 335 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index ecc22365dd..41670b1e81 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -3365,7 +3364,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3379,7 +3378,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3392,7 +3391,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4066,14 +4064,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4088,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4097,10 +4086,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4111,33 +4098,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4153,115 +4118,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4292,13 +4162,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { 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 d3c919a6d8..a422570fff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ 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' || - value.kind === 'ObjectMethod' - ) { - 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/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index fab2111735..f529e2cefe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -232,7 +232,6 @@ const EnvironmentConfigSchema = z.object({ enableUseTypeAnnotations: z.boolean().default(false), enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 954fb6f400..2f5c387645 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 526ab7c7e5..1480ce1610 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { 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 bd938db03e..2eb687dc87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -690,9 +690,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfa..49ff3c256e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 684acaf298..1bdcd03c35 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, LoweredFunction, Place, isRefOrRefValue, @@ -41,20 +40,6 @@ export class IdentifierState { return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - declareTemporary(lvalue: Place, value: Place): void { const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, @@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + infer(instr.value.loweredFunc, func.context); break; } + case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { @@ -115,11 +85,8 @@ function lower(func: HIRFunction): void { logHIRFunction('AnalyseFunction (inner)', func); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { +// infer loweredFunc (inner) with outer function context +function infer(loweredFunc: LoweredFunction, context: Array): void { const mutations = new Map(); for (const operand of loweredFunc.func.context) { if ( @@ -130,15 +97,13 @@ function infer( } } - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } + for (const dep of loweredFunc.func.context) { + CompilerError.invariant(dep.identifier.name !== null, { + reason: 'context refs should always have a name', + description: null, + loc: dep.loc, + suggestions: null, + }); if (isRefOrRefValue(dep.identifier)) { /* @@ -149,8 +114,8 @@ function infer( * render */ dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); + } else { + const effect = mutations.get(dep.identifier.name.value); if (effect !== undefined) { dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; } @@ -176,7 +141,6 @@ function infer( const effect = mutations.get(place.identifier.name.value); if (effect !== undefined) { place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db..5d20a7fa75 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f9521..5b700b23b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40..0e6d1fd592 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd..37394daa8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = true; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36..820f7388dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c2..3584faf699 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20e..6836544c5d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260e..14bf94e770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11..a071dddba6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f..2afc5fd25d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6..3e57b7dc7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e..22728aaf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54ac..60f829cdc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3..299aa5a31d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b..cf85967682 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce93..04b6c4f17f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b530..60fe0808d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480..8822eddcdb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c..da3bb94ed5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901..1ba0d59e17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md index fd03115be1..59ece61d4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md @@ -22,7 +22,7 @@ function Component() { 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced 5 | // within a closure, the `onClick` handler of each item > 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) + | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6) 7 | items.push( data.set(i)} />); 8 | } 9 | return items; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf..f66b970f00 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d5..a7d27bc381 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a..e5ead2479d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index dfe941282e..59c5b92fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06d..8dc4839085 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d1798..3c624de9eb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index dc1a87fe51..2f9cbb7750 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948..0c66dee6a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index ffa5f57b43..3fc047e292 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; const t1 = input;