From 9f35794e90bd461fc337c5f2d22cd840dde8f9f8 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 9 Oct 2024 13:38:40 -0400 Subject: [PATCH 01/18] [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/18] [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/18] [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/18] [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/18] [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/18] [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/18] [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/18] [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 515134d33c066404bf1fb97223021016a2eefb6d Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Tue, 22 Oct 2024 10:35:17 -0700 Subject: [PATCH 09/18] [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 10/18] [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 11/18] [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 12/18] [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 13/18] [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 14/18] [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 15/18] [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 16/18] [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 17/18] [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 18/18] [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}, + ], +};